Migrated to ansible.windows

pull/68298/head
Ansible Core Team 6 years ago committed by Matt Martz
parent 829eb0f1d1
commit 48d4e9370e

File diff suppressed because it is too large Load Diff

@ -1,58 +0,0 @@
#!powershell
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
$results = @{changed=$false}
$parsed_args = Parse-Args $args
$jid = Get-AnsibleParam $parsed_args "jid" -failifempty $true -resultobj $results
$mode = Get-AnsibleParam $parsed_args "mode" -Default "status" -ValidateSet "status","cleanup"
# parsed in from the async_status action plugin
$async_dir = Get-AnsibleParam $parsed_args "_async_dir" -type "path" -failifempty $true
$log_path = [System.IO.Path]::Combine($async_dir, $jid)
If(-not $(Test-Path $log_path))
{
Fail-Json @{ansible_job_id=$jid; started=1; finished=1} "could not find job at '$async_dir'"
}
If($mode -eq "cleanup") {
Remove-Item $log_path -Recurse
Exit-Json @{ansible_job_id=$jid; erased=$log_path}
}
# NOT in cleanup mode, assume regular status mode
# no remote kill mode currently exists, but probably should
# consider log_path + ".pid" file and also unlink that above
$data = $null
Try {
$data_raw = Get-Content $log_path
# TODO: move this into module_utils/powershell.ps1?
$jss = New-Object System.Web.Script.Serialization.JavaScriptSerializer
$data = $jss.DeserializeObject($data_raw)
}
Catch {
If(-not $data_raw) {
# file not written yet? That means it is running
Exit-Json @{results_file=$log_path; ansible_job_id=$jid; started=1; finished=0}
}
Else {
Fail-Json @{ansible_job_id=$jid; results_file=$log_path; started=1; finished=1} "Could not parse job output: $data"
}
}
If (-not $data.ContainsKey("started")) {
$data['finished'] = 1
$data['ansible_job_id'] = $jid
}
ElseIf (-not $data.ContainsKey("finished")) {
$data['finished'] = 0
}
Exit-Json $data

@ -1,516 +0,0 @@
#!powershell
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
Function Get-CustomFacts {
[cmdletBinding()]
param (
[Parameter(mandatory=$false)]
$factpath = $null
)
if (Test-Path -Path $factpath) {
$FactsFiles = Get-ChildItem -Path $factpath | Where-Object -FilterScript {($PSItem.PSIsContainer -eq $false) -and ($PSItem.Extension -eq '.ps1')}
foreach ($FactsFile in $FactsFiles) {
$out = & $($FactsFile.FullName)
$result.ansible_facts.Add("ansible_$(($FactsFile.Name).Split('.')[0])", $out)
}
}
else
{
Add-Warning $result "Non existing path was set for local facts - $factpath"
}
}
Function Get-MachineSid {
# The Machine SID is stored in HKLM:\SECURITY\SAM\Domains\Account and is
# only accessible by the Local System account. This method get's the local
# admin account (ends with -500) and lops it off to get the machine sid.
$machine_sid = $null
try {
$admins_sid = "S-1-5-32-544"
$admin_group = ([Security.Principal.SecurityIdentifier]$admins_sid).Translate([Security.Principal.NTAccount]).Value
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$principal_context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine)
$group_principal = New-Object -TypeName System.DirectoryServices.AccountManagement.GroupPrincipal($principal_context, $admin_group)
$searcher = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalSearcher($group_principal)
$groups = $searcher.FindOne()
foreach ($user in $groups.Members) {
$user_sid = $user.Sid
if ($user_sid.Value.EndsWith("-500")) {
$machine_sid = $user_sid.AccountDomainSid.Value
break
}
}
} catch {
#can fail for any number of reasons, if it does just return the original null
Add-Warning -obj $result -message "Error during machine sid retrieval: $($_.Exception.Message)"
}
return $machine_sid
}
$cim_instances = @{}
Function Get-LazyCimInstance([string]$instance_name, [string]$namespace="Root\CIMV2") {
if(-not $cim_instances.ContainsKey($instance_name)) {
$cim_instances[$instance_name] = $(Get-CimInstance -Namespace $namespace -ClassName $instance_name)
}
return $cim_instances[$instance_name]
}
$result = @{
ansible_facts = @{ }
changed = $false
}
$grouped_subsets = @{
min=[System.Collections.Generic.List[string]]@('date_time','distribution','dns','env','local','platform','powershell_version','user')
network=[System.Collections.Generic.List[string]]@('all_ipv4_addresses','all_ipv6_addresses','interfaces','windows_domain', 'winrm')
hardware=[System.Collections.Generic.List[string]]@('bios','memory','processor','uptime','virtual')
external=[System.Collections.Generic.List[string]]@('facter')
}
# build "all" set from everything mentioned in the group- this means every value must be in at least one subset to be considered legal
$all_set = [System.Collections.Generic.HashSet[string]]@()
foreach($kv in $grouped_subsets.GetEnumerator()) {
[void] $all_set.UnionWith($kv.Value)
}
# dynamically create an "all" subset now that we know what should be in it
$grouped_subsets['all'] = [System.Collections.Generic.List[string]]$all_set
# start with all, build up gather and exclude subsets
$gather_subset = [System.Collections.Generic.HashSet[string]]$grouped_subsets.all
$explicit_subset = [System.Collections.Generic.HashSet[string]]@()
$exclude_subset = [System.Collections.Generic.HashSet[string]]@()
$params = Parse-Args $args -supports_check_mode $true
$factpath = Get-AnsibleParam -obj $params -name "fact_path" -type "path"
$gather_subset_source = Get-AnsibleParam -obj $params -name "gather_subset" -type "list" -default "all"
foreach($item in $gather_subset_source) {
if(([string]$item).StartsWith("!")) {
$item = ([string]$item).Substring(1)
if($item -eq "all") {
$all_minus_min = [System.Collections.Generic.HashSet[string]]@($all_set)
[void] $all_minus_min.ExceptWith($grouped_subsets.min)
[void] $exclude_subset.UnionWith($all_minus_min)
}
elseif($grouped_subsets.ContainsKey($item)) {
[void] $exclude_subset.UnionWith($grouped_subsets[$item])
}
elseif($all_set.Contains($item)) {
[void] $exclude_subset.Add($item)
}
# NB: invalid exclude values are ignored, since that's what posix setup does
}
else {
if($grouped_subsets.ContainsKey($item)) {
[void] $explicit_subset.UnionWith($grouped_subsets[$item])
}
elseif($all_set.Contains($item)) {
[void] $explicit_subset.Add($item)
}
else {
# NB: POSIX setup fails on invalid value; we warn, because we don't implement the same set as POSIX
# and we don't have platform-specific config for this...
Add-Warning $result "invalid value $item specified in gather_subset"
}
}
}
[void] $gather_subset.ExceptWith($exclude_subset)
[void] $gather_subset.UnionWith($explicit_subset)
$ansible_facts = @{
gather_subset=@($gather_subset_source)
module_setup=$true
}
$osversion = [Environment]::OSVersion
if ($osversion.Version -lt [version]"6.2") {
# Server 2008, 2008 R2, and Windows 7 are not tested in CI and we want to let customers know about it before
# removing support altogether.
$version_string = "{0}.{1}" -f ($osversion.Version.Major, $osversion.Version.Minor)
$msg = "Windows version '$version_string' will no longer be supported or tested in the next Ansible release"
Add-DeprecationWarning -obj $result -message $msg -version "2.11"
}
if($gather_subset.Contains('all_ipv4_addresses') -or $gather_subset.Contains('all_ipv6_addresses')) {
$netcfg = Get-LazyCimInstance Win32_NetworkAdapterConfiguration
# TODO: split v4/v6 properly, return in separate keys
$ips = @()
Foreach ($ip in $netcfg.IPAddress) {
If ($ip) {
$ips += $ip
}
}
$ansible_facts += @{
ansible_ip_addresses = $ips
}
}
if($gather_subset.Contains('bios')) {
$win32_bios = Get-LazyCimInstance Win32_Bios
$win32_cs = Get-LazyCimInstance Win32_ComputerSystem
$ansible_facts += @{
ansible_bios_date = $win32_bios.ReleaseDate.ToString("MM/dd/yyyy")
ansible_bios_version = $win32_bios.SMBIOSBIOSVersion
ansible_product_name = $win32_cs.Model.Trim()
ansible_product_serial = $win32_bios.SerialNumber
# ansible_product_version = ([string] $win32_cs.SystemFamily)
}
}
if($gather_subset.Contains('date_time')) {
$datetime = (Get-Date)
$datetime_utc = $datetime.ToUniversalTime()
$date = @{
date = $datetime.ToString("yyyy-MM-dd")
day = $datetime.ToString("dd")
epoch = (Get-Date -UFormat "%s")
hour = $datetime.ToString("HH")
iso8601 = $datetime_utc.ToString("yyyy-MM-ddTHH:mm:ssZ")
iso8601_basic = $datetime.ToString("yyyyMMddTHHmmssffffff")
iso8601_basic_short = $datetime.ToString("yyyyMMddTHHmmss")
iso8601_micro = $datetime_utc.ToString("yyyy-MM-ddTHH:mm:ss.ffffffZ")
minute = $datetime.ToString("mm")
month = $datetime.ToString("MM")
second = $datetime.ToString("ss")
time = $datetime.ToString("HH:mm:ss")
tz = ([System.TimeZoneInfo]::Local.Id)
tz_offset = $datetime.ToString("zzzz")
# Ensure that the weekday is in English
weekday = $datetime.ToString("dddd", [System.Globalization.CultureInfo]::InvariantCulture)
weekday_number = (Get-Date -UFormat "%w")
weeknumber = (Get-Date -UFormat "%W")
year = $datetime.ToString("yyyy")
}
$ansible_facts += @{
ansible_date_time = $date
}
}
if($gather_subset.Contains('distribution')) {
$win32_os = Get-LazyCimInstance Win32_OperatingSystem
$product_type = switch($win32_os.ProductType) {
1 { "workstation" }
2 { "domain_controller" }
3 { "server" }
default { "unknown" }
}
$installation_type = $null
$current_version_path = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
if (Test-Path -LiteralPath $current_version_path) {
$install_type_prop = Get-ItemProperty -LiteralPath $current_version_path -ErrorAction SilentlyContinue
$installation_type = [String]$install_type_prop.InstallationType
}
$ansible_facts += @{
ansible_distribution = $win32_os.Caption
ansible_distribution_version = $osversion.Version.ToString()
ansible_distribution_major_version = $osversion.Version.Major.ToString()
ansible_os_family = "Windows"
ansible_os_name = ($win32_os.Name.Split('|')[0]).Trim()
ansible_os_product_type = $product_type
ansible_os_installation_type = $installation_type
}
}
if($gather_subset.Contains('env')) {
$env_vars = @{ }
foreach ($item in Get-ChildItem Env:) {
$name = $item | Select-Object -ExpandProperty Name
# Powershell ConvertTo-Json fails if string ends with \
$value = ($item | Select-Object -ExpandProperty Value).TrimEnd("\")
$env_vars.Add($name, $value)
}
$ansible_facts += @{
ansible_env = $env_vars
}
}
if($gather_subset.Contains('facter')) {
# See if Facter is on the System Path
Try {
Get-Command facter -ErrorAction Stop > $null
$facter_installed = $true
} Catch {
$facter_installed = $false
}
# Get JSON from Facter, and parse it out.
if ($facter_installed) {
&facter -j | Tee-Object -Variable facter_output > $null
$facts = "$facter_output" | ConvertFrom-Json
ForEach($fact in $facts.PSObject.Properties) {
$fact_name = $fact.Name
$ansible_facts.Add("facter_$fact_name", $fact.Value)
}
}
}
if($gather_subset.Contains('interfaces')) {
$netcfg = Get-LazyCimInstance Win32_NetworkAdapterConfiguration
$ActiveNetcfg = @()
$ActiveNetcfg += $netcfg | Where-Object {$_.ipaddress -ne $null}
$namespaces = Get-LazyCimInstance __Namespace -namespace root
if ($namespaces | Where-Object { $_.Name -eq "StandardCimv" }) {
$net_adapters = Get-LazyCimInstance MSFT_NetAdapter -namespace Root\StandardCimv2
$guid_key = "InterfaceGUID"
$name_key = "Name"
} else {
$net_adapters = Get-LazyCimInstance Win32_NetworkAdapter
$guid_key = "GUID"
$name_key = "NetConnectionID"
}
$formattednetcfg = @()
foreach ($adapter in $ActiveNetcfg)
{
$thisadapter = @{
default_gateway = $null
connection_name = $null
dns_domain = $adapter.dnsdomain
interface_index = $adapter.InterfaceIndex
interface_name = $adapter.description
macaddress = $adapter.macaddress
}
if ($adapter.defaultIPGateway)
{
$thisadapter.default_gateway = $adapter.DefaultIPGateway[0].ToString()
}
$net_adapter = $net_adapters | Where-Object { $_.$guid_key -eq $adapter.SettingID }
if ($net_adapter) {
$thisadapter.connection_name = $net_adapter.$name_key
}
$formattednetcfg += $thisadapter
}
$ansible_facts += @{
ansible_interfaces = $formattednetcfg
}
}
if ($gather_subset.Contains("local") -and $null -ne $factpath) {
# Get any custom facts; results are updated in the
Get-CustomFacts -factpath $factpath
}
if($gather_subset.Contains('memory')) {
$win32_cs = Get-LazyCimInstance Win32_ComputerSystem
$win32_os = Get-LazyCimInstance Win32_OperatingSystem
$ansible_facts += @{
# Win32_PhysicalMemory is empty on some virtual platforms
ansible_memtotal_mb = ([math]::ceiling($win32_cs.TotalPhysicalMemory / 1024 / 1024))
ansible_memfree_mb = ([math]::ceiling($win32_os.FreePhysicalMemory / 1024))
ansible_swaptotal_mb = ([math]::round($win32_os.TotalSwapSpaceSize / 1024))
ansible_pagefiletotal_mb = ([math]::round($win32_os.SizeStoredInPagingFiles / 1024))
ansible_pagefilefree_mb = ([math]::round($win32_os.FreeSpaceInPagingFiles / 1024))
}
}
if($gather_subset.Contains('platform')) {
$win32_cs = Get-LazyCimInstance Win32_ComputerSystem
$win32_os = Get-LazyCimInstance Win32_OperatingSystem
$domain_suffix = $win32_cs.Domain.Substring($win32_cs.Workgroup.length)
$fqdn = $win32_cs.DNSHostname
if( $domain_suffix -ne "")
{
$fqdn = $win32_cs.DNSHostname + "." + $domain_suffix
}
try {
$ansible_reboot_pending = Get-PendingRebootStatus
} catch {
# fails for non-admin users, set to null in this case
$ansible_reboot_pending = $null
}
$ansible_facts += @{
ansible_architecture = $win32_os.OSArchitecture
ansible_domain = $domain_suffix
ansible_fqdn = $fqdn
ansible_hostname = $win32_cs.DNSHostname
ansible_netbios_name = $win32_cs.Name
ansible_kernel = $osversion.Version.ToString()
ansible_nodename = $fqdn
ansible_machine_id = Get-MachineSid
ansible_owner_contact = ([string] $win32_cs.PrimaryOwnerContact)
ansible_owner_name = ([string] $win32_cs.PrimaryOwnerName)
# FUTURE: should this live in its own subset?
ansible_reboot_pending = $ansible_reboot_pending
ansible_system = $osversion.Platform.ToString()
ansible_system_description = ([string] $win32_os.Description)
ansible_system_vendor = $win32_cs.Manufacturer
}
}
if($gather_subset.Contains('powershell_version')) {
$ansible_facts += @{
ansible_powershell_version = ($PSVersionTable.PSVersion.Major)
}
}
if($gather_subset.Contains('processor')) {
$win32_cs = Get-LazyCimInstance Win32_ComputerSystem
$win32_cpu = Get-LazyCimInstance Win32_Processor
if ($win32_cpu -is [array]) {
# multi-socket, pick first
$win32_cpu = $win32_cpu[0]
}
$cpu_list = @( )
for ($i=1; $i -le $win32_cs.NumberOfLogicalProcessors; $i++) {
$cpu_list += $win32_cpu.Manufacturer
$cpu_list += $win32_cpu.Name
}
$ansible_facts += @{
ansible_processor = $cpu_list
ansible_processor_cores = $win32_cpu.NumberOfCores
ansible_processor_count = $win32_cs.NumberOfProcessors
ansible_processor_threads_per_core = ($win32_cpu.NumberOfLogicalProcessors / $win32_cpu.NumberofCores)
ansible_processor_vcpus = $win32_cs.NumberOfLogicalProcessors
}
}
if($gather_subset.Contains('uptime')) {
$win32_os = Get-LazyCimInstance Win32_OperatingSystem
$ansible_facts += @{
ansible_lastboot = $win32_os.lastbootuptime.ToString("u")
ansible_uptime_seconds = $([System.Convert]::ToInt64($(Get-Date).Subtract($win32_os.lastbootuptime).TotalSeconds))
}
}
if($gather_subset.Contains('user')) {
$user = [Security.Principal.WindowsIdentity]::GetCurrent()
$ansible_facts += @{
ansible_user_dir = $env:userprofile
# Win32_UserAccount.FullName is probably the right thing here, but it can be expensive to get on large domains
ansible_user_gecos = ""
ansible_user_id = $env:username
ansible_user_sid = $user.User.Value
}
}
if($gather_subset.Contains('windows_domain')) {
$win32_cs = Get-LazyCimInstance Win32_ComputerSystem
$domain_roles = @{
0 = "Stand-alone workstation"
1 = "Member workstation"
2 = "Stand-alone server"
3 = "Member server"
4 = "Backup domain controller"
5 = "Primary domain controller"
}
$domain_role = $domain_roles.Get_Item([Int32]$win32_cs.DomainRole)
$ansible_facts += @{
ansible_windows_domain = $win32_cs.Domain
ansible_windows_domain_member = $win32_cs.PartOfDomain
ansible_windows_domain_role = $domain_role
}
}
if($gather_subset.Contains('winrm')) {
$winrm_https_listener_parent_paths = Get-ChildItem -Path WSMan:\localhost\Listener -Recurse -ErrorAction SilentlyContinue | `
Where-Object {$_.PSChildName -eq "Transport" -and $_.Value -eq "HTTPS"} | Select-Object PSParentPath
if ($winrm_https_listener_parent_paths -isnot [array]) {
$winrm_https_listener_parent_paths = @($winrm_https_listener_parent_paths)
}
$winrm_https_listener_paths = @()
foreach ($winrm_https_listener_parent_path in $winrm_https_listener_parent_paths) {
$winrm_https_listener_paths += $winrm_https_listener_parent_path.PSParentPath.Substring($winrm_https_listener_parent_path.PSParentPath.LastIndexOf("\"))
}
$https_listeners = @()
foreach ($winrm_https_listener_path in $winrm_https_listener_paths) {
$https_listeners += Get-ChildItem -Path "WSMan:\localhost\Listener$winrm_https_listener_path"
}
$winrm_cert_thumbprints = @()
foreach ($https_listener in $https_listeners) {
$winrm_cert_thumbprints += $https_listener | Where-Object {$_.Name -EQ "CertificateThumbprint" } | Select-Object Value
}
$winrm_cert_expiry = @()
foreach ($winrm_cert_thumbprint in $winrm_cert_thumbprints) {
Try {
$winrm_cert_expiry += Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object Thumbprint -EQ $winrm_cert_thumbprint.Value.ToString().ToUpper() | Select-Object NotAfter
} Catch {
Add-Warning -obj $result -message "Error during certificate expiration retrieval: $($_.Exception.Message)"
}
}
$winrm_cert_expirations = $winrm_cert_expiry | Sort-Object NotAfter
if ($winrm_cert_expirations) {
# this fact was renamed from ansible_winrm_certificate_expires due to collision with ansible_winrm_X connection var pattern
$ansible_facts.Add("ansible_win_rm_certificate_expires", $winrm_cert_expirations[0].NotAfter.ToString("yyyy-MM-dd HH:mm:ss"))
}
}
if($gather_subset.Contains('virtual')) {
$machine_info = Get-LazyCimInstance Win32_ComputerSystem
switch ($machine_info.model) {
"Virtual Machine" {
$machine_type="Hyper-V"
$machine_role="guest"
}
"VMware Virtual Platform" {
$machine_type="VMware"
$machine_role="guest"
}
"VirtualBox" {
$machine_type="VirtualBox"
$machine_role="guest"
}
"HVM domU" {
$machine_type="Xen"
$machine_role="guest"
}
default {
$machine_type="NA"
$machine_role="NA"
}
}
$ansible_facts += @{
ansible_virtualization_role = $machine_role
ansible_virtualization_type = $machine_type
}
}
$result.ansible_facts += $ansible_facts
Exit-Json $result

@ -1,28 +0,0 @@
#!powershell
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
$params = Parse-Args $args -supports_check_mode $true;
$src = Get-AnsibleParam -obj $params -name "src" -type "path" -aliases "path" -failifempty $true;
$result = @{
changed = $false;
}
If (Test-Path -LiteralPath $src -PathType Leaf)
{
$bytes = [System.IO.File]::ReadAllBytes($src);
$result.content = [System.Convert]::ToBase64String($bytes);
$result.encoding = "base64";
Exit-Json $result;
}
ElseIf (Test-Path -LiteralPath $src -PathType Container)
{
Fail-Json $result "Path $src is a directory";
}
Else
{
Fail-Json $result "Path $src is not found";
}

@ -1,225 +0,0 @@
#!powershell
# Copyright: (c) 2015, Phil Schwartz <schwartzmx@gmail.com>
# Copyright: (c) 2015, Trond Hindenes
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
#Requires -Module Ansible.ModuleUtils.SID
$ErrorActionPreference = "Stop"
# win_acl module (File/Resources Permission Additions/Removal)
#Functions
function Get-UserSID {
param(
[String]$AccountName
)
$userSID = $null
$searchAppPools = $false
if ($AccountName.Split("\").Count -gt 1) {
if ($AccountName.Split("\")[0] -eq "IIS APPPOOL") {
$searchAppPools = $true
$AccountName = $AccountName.Split("\")[1]
}
}
if ($searchAppPools) {
Import-Module -Name WebAdministration
$testIISPath = Test-Path -LiteralPath "IIS:"
if ($testIISPath) {
$appPoolObj = Get-ItemProperty -LiteralPath "IIS:\AppPools\$AccountName"
$userSID = $appPoolObj.applicationPoolSid
}
}
else {
$userSID = Convert-ToSID -account_name $AccountName
}
return $userSID
}
$params = Parse-Args $args
Function SetPrivilegeTokens() {
# Set privilege tokens only if admin.
# Admins would have these privs or be able to set these privs in the UI Anyway
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
if ($myWindowsPrincipal.IsInRole($adminRole)) {
# Need to adjust token privs when executing Set-ACL in certain cases.
# e.g. d:\testdir is owned by group in which current user is not a member and no perms are inherited from d:\
# This also sets us up for setting the owner as a feature.
# See the following for details of each privilege
# https://msdn.microsoft.com/en-us/library/windows/desktop/bb530716(v=vs.85).aspx
$privileges = @(
"SeRestorePrivilege", # Grants all write access control to any file, regardless of ACL.
"SeBackupPrivilege", # Grants all read access control to any file, regardless of ACL.
"SeTakeOwnershipPrivilege" # Grants ability to take owernship of an object w/out being granted discretionary access
)
foreach ($privilege in $privileges) {
$state = Get-AnsiblePrivilege -Name $privilege
if ($state -eq $false) {
Set-AnsiblePrivilege -Name $privilege -Value $true
}
}
}
}
$result = @{
changed = $false
}
$path = Get-AnsibleParam -obj $params -name "path" -type "str" -failifempty $true
$user = Get-AnsibleParam -obj $params -name "user" -type "str" -failifempty $true
$rights = Get-AnsibleParam -obj $params -name "rights" -type "str" -failifempty $true
$type = Get-AnsibleParam -obj $params -name "type" -type "str" -failifempty $true -validateset "allow","deny"
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent","present"
$inherit = Get-AnsibleParam -obj $params -name "inherit" -type "str"
$propagation = Get-AnsibleParam -obj $params -name "propagation" -type "str" -default "None" -validateset "InheritOnly","None","NoPropagateInherit"
# We mount the HKCR, HKU, and HKCC registry hives so PS can access them.
# Network paths have no qualifiers so we use -EA SilentlyContinue to ignore that
$path_qualifier = Split-Path -Path $path -Qualifier -ErrorAction SilentlyContinue
if ($path_qualifier -eq "HKCR:" -and (-not (Test-Path -LiteralPath HKCR:\))) {
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT > $null
}
if ($path_qualifier -eq "HKU:" -and (-not (Test-Path -LiteralPath HKU:\))) {
New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS > $null
}
if ($path_qualifier -eq "HKCC:" -and (-not (Test-Path -LiteralPath HKCC:\))) {
New-PSDrive -Name HKCC -PSProvider Registry -Root HKEY_CURRENT_CONFIG > $null
}
If (-Not (Test-Path -LiteralPath $path)) {
Fail-Json -obj $result -message "$path file or directory does not exist on the host"
}
# Test that the user/group is resolvable on the local machine
$sid = Get-UserSID -AccountName $user
if (!$sid) {
Fail-Json -obj $result -message "$user is not a valid user or group on the host machine or domain"
}
If (Test-Path -LiteralPath $path -PathType Leaf) {
$inherit = "None"
}
ElseIf ($null -eq $inherit) {
$inherit = "ContainerInherit, ObjectInherit"
}
# Bug in Set-Acl, Get-Acl where -LiteralPath only works for the Registry provider if the location is in that root
# qualifier. We also don't have a qualifier for a network path so only change if not null
if ($null -ne $path_qualifier) {
Push-Location -LiteralPath $path_qualifier
}
Try {
SetPrivilegeTokens
$path_item = Get-Item -LiteralPath $path -Force
If ($path_item.PSProvider.Name -eq "Registry") {
$colRights = [System.Security.AccessControl.RegistryRights]$rights
}
Else {
$colRights = [System.Security.AccessControl.FileSystemRights]$rights
}
$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]$inherit
$PropagationFlag = [System.Security.AccessControl.PropagationFlags]$propagation
If ($type -eq "allow") {
$objType =[System.Security.AccessControl.AccessControlType]::Allow
}
Else {
$objType =[System.Security.AccessControl.AccessControlType]::Deny
}
$objUser = New-Object System.Security.Principal.SecurityIdentifier($sid)
If ($path_item.PSProvider.Name -eq "Registry") {
$objACE = New-Object System.Security.AccessControl.RegistryAccessRule ($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType)
}
Else {
$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule ($objUser, $colRights, $InheritanceFlag, $PropagationFlag, $objType)
}
$objACL = Get-ACL -LiteralPath $path
# Check if the ACE exists already in the objects ACL list
$match = $false
ForEach($rule in $objACL.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])){
If ($path_item.PSProvider.Name -eq "Registry") {
If (($rule.RegistryRights -eq $objACE.RegistryRights) -And ($rule.AccessControlType -eq $objACE.AccessControlType) -And ($rule.IdentityReference -eq $objACE.IdentityReference) -And ($rule.IsInherited -eq $objACE.IsInherited) -And ($rule.InheritanceFlags -eq $objACE.InheritanceFlags) -And ($rule.PropagationFlags -eq $objACE.PropagationFlags)) {
$match = $true
Break
}
} else {
If (($rule.FileSystemRights -eq $objACE.FileSystemRights) -And ($rule.AccessControlType -eq $objACE.AccessControlType) -And ($rule.IdentityReference -eq $objACE.IdentityReference) -And ($rule.IsInherited -eq $objACE.IsInherited) -And ($rule.InheritanceFlags -eq $objACE.InheritanceFlags) -And ($rule.PropagationFlags -eq $objACE.PropagationFlags)) {
$match = $true
Break
}
}
}
If ($state -eq "present" -And $match -eq $false) {
Try {
$objACL.AddAccessRule($objACE)
If ($path_item.PSProvider.Name -eq "Registry") {
Set-ACL -LiteralPath $path -AclObject $objACL
} else {
(Get-Item -LiteralPath $path).SetAccessControl($objACL)
}
$result.changed = $true
}
Catch {
Fail-Json -obj $result -message "an exception occurred when adding the specified rule - $($_.Exception.Message)"
}
}
ElseIf ($state -eq "absent" -And $match -eq $true) {
Try {
$objACL.RemoveAccessRule($objACE)
If ($path_item.PSProvider.Name -eq "Registry") {
Set-ACL -LiteralPath $path -AclObject $objACL
} else {
(Get-Item -LiteralPath $path).SetAccessControl($objACL)
}
$result.changed = $true
}
Catch {
Fail-Json -obj $result -message "an exception occurred when removing the specified rule - $($_.Exception.Message)"
}
}
Else {
# A rule was attempting to be added but already exists
If ($match -eq $true) {
Exit-Json -obj $result -message "the specified rule already exists"
}
# A rule didn't exist that was trying to be removed
Else {
Exit-Json -obj $result -message "the specified rule does not exist"
}
}
}
Catch {
Fail-Json -obj $result -message "an error occurred when attempting to $state $rights permission(s) on $path for $user - $($_.Exception.Message)"
}
Finally {
# Make sure we revert the location stack to the original path just for cleanups sake
if ($null -ne $path_qualifier) {
Pop-Location
}
}
Exit-Json -obj $result

@ -1,132 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Phil Schwartz <schwartzmx@gmail.com>
# Copyright: (c) 2015, Trond Hindenes
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_acl
version_added: "2.0"
short_description: Set file/directory/registry permissions for a system user or group
description:
- Add or remove rights/permissions for a given user or group for the specified
file, folder, registry key or AppPool identifies.
options:
path:
description:
- The path to the file or directory.
type: str
required: yes
user:
description:
- User or Group to add specified rights to act on src file/folder or
registry key.
type: str
required: yes
state:
description:
- Specify whether to add C(present) or remove C(absent) the specified access rule.
type: str
choices: [ absent, present ]
default: present
type:
description:
- Specify whether to allow or deny the rights specified.
type: str
required: yes
choices: [ allow, deny ]
rights:
description:
- The rights/permissions that are to be allowed/denied for the specified
user or group for the item at C(path).
- If C(path) is a file or directory, rights can be any right under MSDN
FileSystemRights U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx).
- If C(path) is a registry key, rights can be any right under MSDN
RegistryRights U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx).
type: str
required: yes
inherit:
description:
- Inherit flags on the ACL rules.
- Can be specified as a comma separated list, e.g. C(ContainerInherit),
C(ObjectInherit).
- For more information on the choices see MSDN InheritanceFlags enumeration
at U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.inheritanceflags.aspx).
- Defaults to C(ContainerInherit, ObjectInherit) for Directories.
type: str
choices: [ ContainerInherit, ObjectInherit ]
propagation:
description:
- Propagation flag on the ACL rules.
- For more information on the choices see MSDN PropagationFlags enumeration
at U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.propagationflags.aspx).
type: str
choices: [ InheritOnly, None, NoPropagateInherit ]
default: "None"
notes:
- If adding ACL's for AppPool identities (available since 2.3), the Windows
Feature "Web-Scripting-Tools" must be enabled.
seealso:
- module: win_acl_inheritance
- module: win_file
- module: win_owner
- module: win_stat
author:
- Phil Schwartz (@schwartzmx)
- Trond Hindenes (@trondhindenes)
- Hans-Joachim Kliemeck (@h0nIg)
'''
EXAMPLES = r'''
- name: Restrict write and execute access to User Fed-Phil
win_acl:
user: Fed-Phil
path: C:\Important\Executable.exe
type: deny
rights: ExecuteFile,Write
- name: Add IIS_IUSRS allow rights
win_acl:
path: C:\inetpub\wwwroot\MySite
user: IIS_IUSRS
rights: FullControl
type: allow
state: present
inherit: ContainerInherit, ObjectInherit
propagation: 'None'
- name: Set registry key right
win_acl:
path: HKCU:\Bovine\Key
user: BUILTIN\Users
rights: EnumerateSubKeys
type: allow
state: present
inherit: ContainerInherit, ObjectInherit
propagation: 'None'
- name: Remove FullControl AccessRule for IIS_IUSRS
win_acl:
path: C:\inetpub\wwwroot\MySite
user: IIS_IUSRS
rights: FullControl
type: allow
state: absent
inherit: ContainerInherit, ObjectInherit
propagation: 'None'
- name: Deny Intern
win_acl:
path: C:\Administrator\Documents
user: Intern
rights: Read,Write,Modify,FullControl,Delete
type: deny
state: present
'''

@ -1,67 +0,0 @@
#!powershell
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false
$result = @{
changed = $false
}
$path = Get-AnsibleParam -obj $params "path" -type "path" -failifempty $true
$state = Get-AnsibleParam -obj $params "state" -type "str" -default "absent" -validateSet "present","absent" -resultobj $result
$reorganize = Get-AnsibleParam -obj $params "reorganize" -type "bool" -default $false -resultobj $result
If (-Not (Test-Path -LiteralPath $path)) {
Fail-Json $result "$path file or directory does not exist on the host"
}
Try {
$objACL = Get-ACL -LiteralPath $path
# AreAccessRulesProtected - $false if inheritance is set ,$true if inheritance is not set
$inheritanceDisabled = $objACL.AreAccessRulesProtected
If (($state -eq "present") -And $inheritanceDisabled) {
# second parameter is ignored if first=$False
$objACL.SetAccessRuleProtection($False, $False)
If ($reorganize) {
# it wont work without intermediate save, state would be the same
Set-ACL -LiteralPath $path -AclObject $objACL -WhatIf:$check_mode
$result.changed = $true
$objACL = Get-ACL -LiteralPath $path
# convert explicit ACE to inherited ACE
ForEach($inheritedRule in $objACL.Access) {
If (-not $inheritedRule.IsInherited) {
Continue
}
ForEach($explicitRrule in $objACL.Access) {
If ($explicitRrule.IsInherited) {
Continue
}
If (($inheritedRule.FileSystemRights -eq $explicitRrule.FileSystemRights) -And ($inheritedRule.AccessControlType -eq $explicitRrule.AccessControlType) -And ($inheritedRule.IdentityReference -eq $explicitRrule.IdentityReference) -And ($inheritedRule.InheritanceFlags -eq $explicitRrule.InheritanceFlags) -And ($inheritedRule.PropagationFlags -eq $explicitRrule.PropagationFlags)) {
$objACL.RemoveAccessRule($explicitRrule)
}
}
}
}
Set-ACL -LiteralPath $path -AclObject $objACL -WhatIf:$check_mode
$result.changed = $true
} Elseif (($state -eq "absent") -And (-not $inheritanceDisabled)) {
$objACL.SetAccessRuleProtection($True, $reorganize)
Set-ACL -LiteralPath $path -AclObject $objACL -WhatIf:$check_mode
$result.changed = $true
}
} Catch {
Fail-Json $result "an error occurred when attempting to disable inheritance: $($_.Exception.Message)"
}
Exit-Json $result

@ -1,70 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_acl_inheritance
version_added: "2.1"
short_description: Change ACL inheritance
description:
- Change ACL (Access Control List) inheritance and optionally copy inherited ACE's (Access Control Entry) to dedicated ACE's or vice versa.
options:
path:
description:
- Path to be used for changing inheritance
required: yes
type: path
state:
description:
- Specify whether to enable I(present) or disable I(absent) ACL inheritance.
type: str
choices: [ absent, present ]
default: absent
reorganize:
description:
- For P(state) = I(absent), indicates if the inherited ACE's should be copied from the parent directory.
This is necessary (in combination with removal) for a simple ACL instead of using multiple ACE deny entries.
- For P(state) = I(present), indicates if the inherited ACE's should be deduplicated compared to the parent directory.
This removes complexity of the ACL structure.
type: bool
default: no
seealso:
- module: win_acl
- module: win_file
- module: win_stat
author:
- Hans-Joachim Kliemeck (@h0nIg)
'''
EXAMPLES = r'''
- name: Disable inherited ACE's
win_acl_inheritance:
path: C:\apache
state: absent
- name: Disable and copy inherited ACE's
win_acl_inheritance:
path: C:\apache
state: absent
reorganize: yes
- name: Enable and remove dedicated ACE's
win_acl_inheritance:
path: C:\apache
state: present
reorganize: yes
'''
RETURN = r'''
'''

@ -1,260 +0,0 @@
#!powershell
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
$store_name_values = ([System.Security.Cryptography.X509Certificates.StoreName]).GetEnumValues() | ForEach-Object { $_.ToString() }
$store_location_values = ([System.Security.Cryptography.X509Certificates.StoreLocation]).GetEnumValues() | ForEach-Object { $_.ToString() }
$spec = @{
options = @{
state = @{ type = "str"; default = "present"; choices = "absent", "exported", "present" }
path = @{ type = "path" }
thumbprint = @{ type = "str" }
store_name = @{ type = "str"; default = "My"; choices = $store_name_values }
store_location = @{ type = "str"; default = "LocalMachine"; choices = $store_location_values }
password = @{ type = "str"; no_log = $true }
key_exportable = @{ type = "bool"; default = $true }
key_storage = @{ type = "str"; default = "default"; choices = "default", "machine", "user" }
file_type = @{ type = "str"; default = "der"; choices = "der", "pem", "pkcs12" }
}
required_if = @(
@("state", "absent", @("path", "thumbprint"), $true),
@("state", "exported", @("path", "thumbprint")),
@("state", "present", @("path"))
)
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
Function Get-CertFile($module, $path, $password, $key_exportable, $key_storage) {
# parses a certificate file and returns X509Certificate2Collection
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
$module.FailJson("File at '$path' either does not exist or is not a file")
}
# must set at least the PersistKeySet flag so that the PrivateKey
# is stored in a permanent container and not deleted once the handle
# is gone.
$store_flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
$key_storage = $key_storage.substring(0,1).ToUpper() + $key_storage.substring(1).ToLower()
$store_flags = $store_flags -bor [Enum]::Parse([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags], "$($key_storage)KeySet")
if ($key_exportable) {
$store_flags = $store_flags -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
}
# TODO: If I'm feeling adventurours, write code to parse PKCS#12 PEM encoded
# file as .NET does not have an easy way to import this
$certs = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection
try {
$certs.Import($path, $password, $store_flags)
} catch {
$module.FailJson("Failed to load cert from file: $($_.Exception.Message)", $_)
}
return $certs
}
Function New-CertFile($module, $cert, $path, $type, $password) {
$content_type = switch ($type) {
"pem" { [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert }
"der" { [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert }
"pkcs12" { [System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12 }
}
if ($type -eq "pkcs12") {
$missing_key = $false
if ($null -eq $cert.PrivateKey) {
$missing_key = $true
} elseif ($cert.PrivateKey.CspKeyContainerInfo.Exportable -eq $false) {
$missing_key = $true
}
if ($missing_key) {
$module.FailJson("Cannot export cert with key as PKCS12 when the key is not marked as exportable or not accessible by the current user")
}
}
if (Test-Path -LiteralPath $path) {
Remove-Item -LiteralPath $path -Force
$module.Result.changed = $true
}
try {
$cert_bytes = $cert.Export($content_type, $password)
} catch {
$module.FailJson("Failed to export certificate as bytes: $($_.Exception.Message)", $_)
}
# Need to manually handle a PEM file
if ($type -eq "pem") {
$cert_content = "-----BEGIN CERTIFICATE-----`r`n"
$base64_string = [System.Convert]::ToBase64String($cert_bytes, [System.Base64FormattingOptions]::InsertLineBreaks)
$cert_content += $base64_string
$cert_content += "`r`n-----END CERTIFICATE-----"
$file_encoding = [System.Text.Encoding]::ASCII
$cert_bytes = $file_encoding.GetBytes($cert_content)
} elseif ($type -eq "pkcs12") {
$module.Result.key_exported = $false
if ($null -ne $cert.PrivateKey) {
$module.Result.key_exportable = $cert.PrivateKey.CspKeyContainerInfo.Exportable
}
}
if (-not $module.CheckMode) {
try {
[System.IO.File]::WriteAllBytes($path, $cert_bytes)
} catch [System.ArgumentNullException] {
$module.FailJson("Failed to write cert to file, cert was null: $($_.Exception.Message)", $_)
} catch [System.IO.IOException] {
$module.FailJson("Failed to write cert to file due to IO Exception: $($_.Exception.Message)", $_)
} catch [System.UnauthorizedAccessException] {
$module.FailJson("Failed to write cert to file due to permissions: $($_.Exception.Message)", $_)
} catch {
$module.FailJson("Failed to write cert to file: $($_.Exception.Message)", $_)
}
}
$module.Result.changed = $true
}
Function Get-CertFileType($path, $password) {
$certs = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection
try {
$certs.Import($path, $password, 0)
} catch [System.Security.Cryptography.CryptographicException] {
# the file is a pkcs12 we just had the wrong password
return "pkcs12"
} catch {
return "unknown"
}
$file_contents = Get-Content -LiteralPath $path -Raw
if ($file_contents.StartsWith("-----BEGIN CERTIFICATE-----")) {
return "pem"
} elseif ($file_contents.StartsWith("-----BEGIN PKCS7-----")) {
return "pkcs7-ascii"
} elseif ($certs.Count -gt 1) {
# multiple certs must be pkcs7
return "pkcs7-binary"
} elseif ($certs[0].HasPrivateKey) {
return "pkcs12"
} elseif ($path.EndsWith(".pfx") -or $path.EndsWith(".p12")) {
# no way to differenciate a pfx with a der file so we must rely on the
# extension
return "pkcs12"
} else {
return "der"
}
}
$state = $module.Params.state
$path = $module.Params.path
$thumbprint = $module.Params.thumbprint
$store_name = [System.Security.Cryptography.X509Certificates.StoreName]"$($module.Params.store_name)"
$store_location = [System.Security.Cryptography.X509Certificates.Storelocation]"$($module.Params.store_location)"
$password = $module.Params.password
$key_exportable = $module.Params.key_exportable
$key_storage = $module.Params.key_storage
$file_type = $module.Params.file_type
$module.Result.thumbprints = @()
$store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $store_name, $store_location
try {
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
} catch [System.Security.Cryptography.CryptographicException] {
$module.FailJson("Unable to open the store as it is not readable: $($_.Exception.Message)", $_)
} catch [System.Security.SecurityException] {
$module.FailJson("Unable to open the store with the current permissions: $($_.Exception.Message)", $_)
} catch {
$module.FailJson("Unable to open the store: $($_.Exception.Message)", $_)
}
$store_certificates = $store.Certificates
try {
if ($state -eq "absent") {
$cert_thumbprints = @()
if ($null -ne $path) {
$certs = Get-CertFile -module $module -path $path -password $password -key_exportable $key_exportable -key_storage $key_storage
foreach ($cert in $certs) {
$cert_thumbprints += $cert.Thumbprint
}
} elseif ($null -ne $thumbprint) {
$cert_thumbprints += $thumbprint
}
foreach ($cert_thumbprint in $cert_thumbprints) {
$module.Result.thumbprints += $cert_thumbprint
$found_certs = $store_certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $cert_thumbprint, $false)
if ($found_certs.Count -gt 0) {
foreach ($found_cert in $found_certs) {
try {
if (-not $module.CheckMode) {
$store.Remove($found_cert)
}
} catch [System.Security.SecurityException] {
$module.FailJson("Unable to remove cert with thumbprint '$cert_thumbprint' with current permissions: $($_.Exception.Message)", $_)
} catch {
$module.FailJson("Unable to remove cert with thumbprint '$cert_thumbprint': $($_.Exception.Message)", $_)
}
$module.Result.changed = $true
}
}
}
} elseif ($state -eq "exported") {
# TODO: Add support for PKCS7 and exporting a cert chain
$module.Result.thumbprints += $thumbprint
$export = $true
if (Test-Path -LiteralPath $path -PathType Container) {
$module.FailJson("Cannot export cert to path '$path' as it is a directory")
} elseif (Test-Path -LiteralPath $path -PathType Leaf) {
$actual_cert_type = Get-CertFileType -path $path -password $password
if ($actual_cert_type -eq $file_type) {
try {
$certs = Get-CertFile -module $module -path $path -password $password -key_exportable $key_exportable -key_storage $key_storage
} catch {
# failed to load the file so we set the thumbprint to something
# that will fail validation
$certs = @{Thumbprint = $null}
}
if ($certs.Thumbprint -eq $thumbprint) {
$export = $false
}
}
}
if ($export) {
$found_certs = $store_certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $thumbprint, $false)
if ($found_certs.Count -ne 1) {
$module.FailJson("Found $($found_certs.Count) certs when only expecting 1")
}
New-CertFile -module $module -cert $found_certs -path $path -type $file_type -password $password
}
} else {
$certs = Get-CertFile -module $module -path $path -password $password -key_exportable $key_exportable -key_storage $key_storage
foreach ($cert in $certs) {
$module.Result.thumbprints += $cert.Thumbprint
$found_certs = $store_certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $cert.Thumbprint, $false)
if ($found_certs.Count -eq 0) {
try {
if (-not $module.CheckMode) {
$store.Add($cert)
}
} catch [System.Security.Cryptography.CryptographicException] {
$module.FailJson("Unable to import certificate with thumbprint '$($cert.Thumbprint)' with the current permissions: $($_.Exception.Message)", $_)
} catch {
$module.FailJson("Unable to import certificate with thumbprint '$($cert.Thumbprint)': $($_.Exception.Message)", $_)
}
$module.Result.changed = $true
}
}
}
} finally {
$store.Close()
}
$module.ExitJson()

@ -1,208 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_certificate_store
version_added: '2.5'
short_description: Manages the certificate store
description:
- Used to import/export and remove certificates and keys from the local
certificate store.
- This module is not used to create certificates and will only manage existing
certs as a file or in the store.
- It can be used to import PEM, DER, P7B, PKCS12 (PFX) certificates and export
PEM, DER and PKCS12 certificates.
options:
state:
description:
- If C(present), will ensure that the certificate at I(path) is imported
into the certificate store specified.
- If C(absent), will ensure that the certificate specified by I(thumbprint)
or the thumbprint of the cert at I(path) is removed from the store
specified.
- If C(exported), will ensure the file at I(path) is a certificate
specified by I(thumbprint).
- When exporting a certificate, if I(path) is a directory then the module
will fail, otherwise the file will be replaced if needed.
type: str
choices: [ absent, exported, present ]
default: present
path:
description:
- The path to a certificate file.
- This is required when I(state) is C(present) or C(exported).
- When I(state) is C(absent) and I(thumbprint) is not specified, the
thumbprint is derived from the certificate at this path.
type: path
thumbprint:
description:
- The thumbprint as a hex string to either export or remove.
- See the examples for how to specify the thumbprint.
type: str
store_name:
description:
- The store name to use when importing a certificate or searching for a
certificate.
- "C(AddressBook): The X.509 certificate store for other users"
- "C(AuthRoot): The X.509 certificate store for third-party certificate authorities (CAs)"
- "C(CertificateAuthority): The X.509 certificate store for intermediate certificate authorities (CAs)"
- "C(Disallowed): The X.509 certificate store for revoked certificates"
- "C(My): The X.509 certificate store for personal certificates"
- "C(Root): The X.509 certificate store for trusted root certificate authorities (CAs)"
- "C(TrustedPeople): The X.509 certificate store for directly trusted people and resources"
- "C(TrustedPublisher): The X.509 certificate store for directly trusted publishers"
type: str
choices:
- AddressBook
- AuthRoot
- CertificateAuthority
- Disallowed
- My
- Root
- TrustedPeople
- TrustedPublisher
default: My
store_location:
description:
- The store location to use when importing a certificate or searching for a
certificate.
choices: [ CurrentUser, LocalMachine ]
default: LocalMachine
password:
description:
- The password of the pkcs12 certificate key.
- This is used when reading a pkcs12 certificate file or the password to
set when C(state=exported) and C(file_type=pkcs12).
- If the pkcs12 file has no password set or no password should be set on
the exported file, do not set this option.
type: str
key_exportable:
description:
- Whether to allow the private key to be exported.
- If C(no), then this module and other process will only be able to export
the certificate and the private key cannot be exported.
- Used when C(state=present) only.
type: bool
default: yes
key_storage:
description:
- Specifies where Windows will store the private key when it is imported.
- When set to C(default), the default option as set by Windows is used, typically C(user).
- When set to C(machine), the key is stored in a path accessible by various
users.
- When set to C(user), the key is stored in a path only accessible by the
current user.
- Used when C(state=present) only and cannot be changed once imported.
- See U(https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509keystorageflags.aspx)
for more details.
type: str
choices: [ default, machine, user ]
default: default
file_type:
description:
- The file type to export the certificate as when C(state=exported).
- C(der) is a binary ASN.1 encoded file.
- C(pem) is a base64 encoded file of a der file in the OpenSSL form.
- C(pkcs12) (also known as pfx) is a binary container that contains both
the certificate and private key unlike the other options.
- When C(pkcs12) is set and the private key is not exportable or accessible
by the current user, it will throw an exception.
type: str
choices: [ der, pem, pkcs12 ]
default: der
notes:
- Some actions on PKCS12 certificates and keys may fail with the error
C(the specified network password is not correct), either use CredSSP or
Kerberos with credential delegation, or use C(become) to bypass these
restrictions.
- The certificates must be located on the Windows host to be set with I(path).
- When importing a certificate for usage in IIS, it is generally required
to use the C(machine) key_storage option, as both C(default) and C(user)
will make the private key unreadable to IIS APPPOOL identities and prevent
binding the certificate to the https endpoint.
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Import a certificate
win_certificate_store:
path: C:\Temp\cert.pem
state: present
- name: Import pfx certificate that is password protected
win_certificate_store:
path: C:\Temp\cert.pfx
state: present
password: VeryStrongPasswordHere!
become: yes
become_method: runas
- name: Import pfx certificate without password and set private key as un-exportable
win_certificate_store:
path: C:\Temp\cert.pfx
state: present
key_exportable: no
# usually you don't set this here but it is for illustrative purposes
vars:
ansible_winrm_transport: credssp
- name: Remove a certificate based on file thumbprint
win_certificate_store:
path: C:\Temp\cert.pem
state: absent
- name: Remove a certificate based on thumbprint
win_certificate_store:
thumbprint: BD7AF104CF1872BDB518D95C9534EA941665FD27
state: absent
- name: Remove certificate based on thumbprint is CurrentUser/TrustedPublishers store
win_certificate_store:
thumbprint: BD7AF104CF1872BDB518D95C9534EA941665FD27
state: absent
store_location: CurrentUser
store_name: TrustedPublisher
- name: Export certificate as der encoded file
win_certificate_store:
path: C:\Temp\cert.cer
state: exported
file_type: der
- name: Export certificate and key as pfx encoded file
win_certificate_store:
path: C:\Temp\cert.pfx
state: exported
file_type: pkcs12
password: AnotherStrongPass!
become: yes
become_method: runas
become_user: SYSTEM
- name: Import certificate be used by IIS
win_certificate_store:
path: C:\Temp\cert.pfx
file_type: pkcs12
password: StrongPassword!
store_location: LocalMachine
key_storage: machine
state: present
'''
RETURN = r'''
thumbprints:
description: A list of certificate thumbprints that were touched by the
module.
returned: success
type: list
sample: ["BC05633694E675449136679A658281F17A191087"]
'''

@ -1,78 +0,0 @@
#!powershell
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.CommandUtil
#Requires -Module Ansible.ModuleUtils.FileUtil
# TODO: add check mode support
Set-StrictMode -Version 2
$ErrorActionPreference = 'Stop'
$params = Parse-Args $args -supports_check_mode $false
$raw_command_line = Get-AnsibleParam -obj $params -name "_raw_params" -type "str" -failifempty $true
$chdir = Get-AnsibleParam -obj $params -name "chdir" -type "path"
$creates = Get-AnsibleParam -obj $params -name "creates" -type "path"
$removes = Get-AnsibleParam -obj $params -name "removes" -type "path"
$stdin = Get-AnsibleParam -obj $params -name "stdin" -type "str"
$output_encoding_override = Get-AnsibleParam -obj $params -name "output_encoding_override" -type "str"
$raw_command_line = $raw_command_line.Trim()
$result = @{
changed = $true
cmd = $raw_command_line
}
if ($creates -and $(Test-AnsiblePath -Path $creates)) {
Exit-Json @{msg="skipped, since $creates exists";cmd=$raw_command_line;changed=$false;skipped=$true;rc=0}
}
if ($removes -and -not $(Test-AnsiblePath -Path $removes)) {
Exit-Json @{msg="skipped, since $removes does not exist";cmd=$raw_command_line;changed=$false;skipped=$true;rc=0}
}
$command_args = @{
command = $raw_command_line
}
if ($chdir) {
$command_args['working_directory'] = $chdir
}
if ($stdin) {
$command_args['stdin'] = $stdin
}
if ($output_encoding_override) {
$command_args['output_encoding_override'] = $output_encoding_override
}
$start_datetime = [DateTime]::UtcNow
try {
$command_result = Run-Command @command_args
} catch {
$result.changed = $false
try {
$result.rc = $_.Exception.NativeErrorCode
} catch {
$result.rc = 2
}
Fail-Json -obj $result -message $_.Exception.Message
}
$result.stdout = $command_result.stdout
$result.stderr = $command_result.stderr
$result.rc = $command_result.rc
$end_datetime = [DateTime]::UtcNow
$result.start = $start_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff")
$result.end = $end_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff")
$result.delta = $($end_datetime - $start_datetime).ToString("h\:mm\:ss\.ffffff")
If ($result.rc -ne 0) {
Fail-Json -obj $result -message "non-zero return code"
}
Exit-Json $result

@ -1,136 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2016, Ansible, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_command
short_description: Executes a command on a remote Windows node
version_added: 2.2
description:
- The C(win_command) module takes the command name followed by a list of space-delimited arguments.
- The given command will be executed on all selected nodes. It will not be
processed through the shell, so variables like C($env:HOME) and operations
like C("<"), C(">"), C("|"), and C(";") will not work (use the M(win_shell)
module if you need these features).
- For non-Windows targets, use the M(command) module instead.
options:
free_form:
description:
- The C(win_command) module takes a free form command to run.
- There is no parameter actually named 'free form'. See the examples!
type: str
required: yes
creates:
description:
- A path or path filter pattern; when the referenced path exists on the target host, the task will be skipped.
type: path
removes:
description:
- A path or path filter pattern; when the referenced path B(does not) exist on the target host, the task will be skipped.
type: path
chdir:
description:
- Set the specified path as the current working directory before executing a command.
type: path
stdin:
description:
- Set the stdin of the command directly to the specified value.
type: str
version_added: '2.5'
output_encoding_override:
description:
- This option overrides the encoding of stdout/stderr output.
- You can use this option when you need to run a command which ignore the console's codepage.
- You should only need to use this option in very rare circumstances.
- This value can be any valid encoding C(Name) based on the output of C([System.Text.Encoding]::GetEncodings()).
See U(https://docs.microsoft.com/dotnet/api/system.text.encoding.getencodings).
type: str
version_added: '2.10'
notes:
- If you want to run a command through a shell (say you are using C(<),
C(>), C(|), etc), you actually want the M(win_shell) module instead. The
C(win_command) module is much more secure as it's not affected by the user's
environment.
- C(creates), C(removes), and C(chdir) can be specified after the command. For instance, if you only want to run a command if a certain file does not
exist, use this.
seealso:
- module: command
- module: psexec
- module: raw
- module: win_psexec
- module: win_shell
author:
- Matt Davis (@nitzmahone)
'''
EXAMPLES = r'''
- name: Save the result of 'whoami' in 'whoami_out'
win_command: whoami
register: whoami_out
- name: Run command that only runs if folder exists and runs from a specific folder
win_command: wbadmin -backupTarget:C:\backup\
args:
chdir: C:\somedir\
creates: C:\backup\
- name: Run an executable and send data to the stdin for the executable
win_command: powershell.exe -
args:
stdin: Write-Host test
'''
RETURN = r'''
msg:
description: changed
returned: always
type: bool
sample: true
start:
description: The command execution start time
returned: always
type: str
sample: '2016-02-25 09:18:26.429568'
end:
description: The command execution end time
returned: always
type: str
sample: '2016-02-25 09:18:26.755339'
delta:
description: The command execution delta time
returned: always
type: str
sample: '0:00:00.325771'
stdout:
description: The command standard output
returned: always
type: str
sample: 'Clustering node rabbit@slave1 with rabbit@master ...'
stderr:
description: The command standard error
returned: always
type: str
sample: 'ls: cannot access foo: No such file or directory'
cmd:
description: The command executed by the task
returned: always
type: str
sample: 'rabbitmqctl join_cluster rabbit@master'
rc:
description: The command return code (0 means success)
returned: always
type: int
sample: 0
stdout_lines:
description: The command standard output split in lines
returned: always
type: list
sample: [u'Clustering node rabbit@slave1 with rabbit@master ...']
'''

@ -1,403 +0,0 @@
#!powershell
# Copyright: (c) 2015, Jon Hawkesworth (@jhawkesworth) <figs@unity.demon.co.uk>
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.Backup
$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
# there are 4 modes to win_copy which are driven by the action plugins:
# explode: src is a zip file which needs to be extracted to dest, for use with multiple files
# query: win_copy action plugin wants to get the state of remote files to check whether it needs to send them
# remote: all copy action is happening remotely (remote_src=True)
# single: a single file has been copied, also used with template
$copy_mode = Get-AnsibleParam -obj $params -name "_copy_mode" -type "str" -default "single" -validateset "explode","query","remote","single"
# used in explode, remote and single mode
$src = Get-AnsibleParam -obj $params -name "src" -type "path" -failifempty ($copy_mode -in @("explode","process","single"))
$dest = Get-AnsibleParam -obj $params -name "dest" -type "path" -failifempty $true
$backup = Get-AnsibleParam -obj $params -name "backup" -type "bool" -default $false
# used in single mode
$original_basename = Get-AnsibleParam -obj $params -name "_original_basename" -type "str"
# used in query and remote mode
$force = Get-AnsibleParam -obj $params -name "force" -type "bool" -default $true
# used in query mode, contains the local files/directories/symlinks that are to be copied
$files = Get-AnsibleParam -obj $params -name "files" -type "list"
$directories = Get-AnsibleParam -obj $params -name "directories" -type "list"
$result = @{
changed = $false
}
if ($diff_mode) {
$result.diff = @{}
}
Function Copy-File($source, $dest) {
$diff = ""
$copy_file = $false
$source_checksum = $null
if ($force) {
$source_checksum = Get-FileChecksum -path $source
}
if (Test-Path -LiteralPath $dest -PathType Container) {
Fail-Json -obj $result -message "cannot copy file from '$source' to '$dest': dest is already a folder"
} elseif (Test-Path -LiteralPath $dest -PathType Leaf) {
if ($force) {
$target_checksum = Get-FileChecksum -path $dest
if ($source_checksum -ne $target_checksum) {
$copy_file = $true
}
}
} else {
$copy_file = $true
}
if ($copy_file) {
$file_dir = [System.IO.Path]::GetDirectoryName($dest)
# validate the parent dir is not a file and that it exists
if (Test-Path -LiteralPath $file_dir -PathType Leaf) {
Fail-Json -obj $result -message "cannot copy file from '$source' to '$dest': object at dest parent dir is not a folder"
} elseif (-not (Test-Path -LiteralPath $file_dir)) {
# directory doesn't exist, need to create
New-Item -Path $file_dir -ItemType Directory -WhatIf:$check_mode | Out-Null
$diff += "+$file_dir\`n"
}
if ($backup) {
$result.backup_file = Backup-File -path $dest -WhatIf:$check_mode
}
if (Test-Path -LiteralPath $dest -PathType Leaf) {
Remove-Item -LiteralPath $dest -Force -Recurse -WhatIf:$check_mode | Out-Null
$diff += "-$dest`n"
}
if (-not $check_mode) {
# cannot run with -WhatIf:$check_mode as if the parent dir didn't
# exist and was created above would still not exist in check mode
Copy-Item -LiteralPath $source -Destination $dest -Force | Out-Null
}
$diff += "+$dest`n"
$result.changed = $true
}
# ugly but to save us from running the checksum twice, let's return it for
# the main code to add it to $result
return ,@{ diff = $diff; checksum = $source_checksum }
}
Function Copy-Folder($source, $dest) {
$diff = ""
if (-not (Test-Path -LiteralPath $dest -PathType Container)) {
$parent_dir = [System.IO.Path]::GetDirectoryName($dest)
if (Test-Path -LiteralPath $parent_dir -PathType Leaf) {
Fail-Json -obj $result -message "cannot copy file from '$source' to '$dest': object at dest parent dir is not a folder"
}
if (Test-Path -LiteralPath $dest -PathType Leaf) {
Fail-Json -obj $result -message "cannot copy folder from '$source' to '$dest': dest is already a file"
}
New-Item -Path $dest -ItemType Container -WhatIf:$check_mode | Out-Null
$diff += "+$dest\`n"
$result.changed = $true
}
$child_items = Get-ChildItem -LiteralPath $source -Force
foreach ($child_item in $child_items) {
$dest_child_path = Join-Path -Path $dest -ChildPath $child_item.Name
if ($child_item.PSIsContainer) {
$diff += (Copy-Folder -source $child_item.Fullname -dest $dest_child_path)
} else {
$diff += (Copy-File -source $child_item.Fullname -dest $dest_child_path).diff
}
}
return $diff
}
Function Get-FileSize($path) {
$file = Get-Item -LiteralPath $path -Force
if ($file.PSIsContainer) {
$size = (Get-ChildItem -Literalpath $file.FullName -Recurse -Force | `
Where-Object { $_.PSObject.Properties.Name -contains 'Length' } | `
Measure-Object -Property Length -Sum).Sum
if ($null -eq $size) {
$size = 0
}
} else {
$size = $file.Length
}
$size
}
Function Extract-Zip($src, $dest) {
$archive = [System.IO.Compression.ZipFile]::Open($src, [System.IO.Compression.ZipArchiveMode]::Read, [System.Text.Encoding]::UTF8)
foreach ($entry in $archive.Entries) {
$archive_name = $entry.FullName
# FullName may be appended with / or \, determine if it is padded and remove it
$padding_length = $archive_name.Length % 4
if ($padding_length -eq 0) {
$is_dir = $false
$base64_name = $archive_name
} elseif ($padding_length -eq 1) {
$is_dir = $true
if ($archive_name.EndsWith("/") -or $archive_name.EndsWith("`\")) {
$base64_name = $archive_name.Substring(0, $archive_name.Length - 1)
} else {
throw "invalid base64 archive name '$archive_name'"
}
} else {
throw "invalid base64 length '$archive_name'"
}
# to handle unicode character, win_copy action plugin has encoded the filename
$decoded_archive_name = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64_name))
# re-add the / to the entry full name if it was a directory
if ($is_dir) {
$decoded_archive_name = "$decoded_archive_name/"
}
$entry_target_path = [System.IO.Path]::Combine($dest, $decoded_archive_name)
$entry_dir = [System.IO.Path]::GetDirectoryName($entry_target_path)
if (-not (Test-Path -LiteralPath $entry_dir)) {
New-Item -Path $entry_dir -ItemType Directory -WhatIf:$check_mode | Out-Null
}
if ($is_dir -eq $false) {
if (-not $check_mode) {
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $entry_target_path, $true)
}
}
}
$archive.Dispose() # release the handle of the zip file
}
Function Extract-ZipLegacy($src, $dest) {
if (-not (Test-Path -LiteralPath $dest)) {
New-Item -Path $dest -ItemType Directory -WhatIf:$check_mode | Out-Null
}
$shell = New-Object -ComObject Shell.Application
$zip = $shell.NameSpace($src)
$dest_path = $shell.NameSpace($dest)
foreach ($entry in $zip.Items()) {
$is_dir = $entry.IsFolder
$encoded_archive_entry = $entry.Name
# to handle unicode character, win_copy action plugin has encoded the filename
$decoded_archive_entry = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encoded_archive_entry))
if ($is_dir) {
$decoded_archive_entry = "$decoded_archive_entry/"
}
$entry_target_path = [System.IO.Path]::Combine($dest, $decoded_archive_entry)
$entry_dir = [System.IO.Path]::GetDirectoryName($entry_target_path)
if (-not (Test-Path -LiteralPath $entry_dir)) {
New-Item -Path $entry_dir -ItemType Directory -WhatIf:$check_mode | Out-Null
}
if ($is_dir -eq $false -and (-not $check_mode)) {
# https://msdn.microsoft.com/en-us/library/windows/desktop/bb787866.aspx
# From Folder.CopyHere documentation, 1044 means:
# - 1024: do not display a user interface if an error occurs
# - 16: respond with "yes to all" for any dialog box that is displayed
# - 4: do not display a progress dialog box
$dest_path.CopyHere($entry, 1044)
# once file is extraced, we need to rename it with non base64 name
$combined_encoded_path = [System.IO.Path]::Combine($dest, $encoded_archive_entry)
Move-Item -LiteralPath $combined_encoded_path -Destination $entry_target_path -Force | Out-Null
}
}
}
if ($copy_mode -eq "query") {
# we only return a list of files/directories that need to be copied over
# the source of the local file will be the key used
$changed_files = @()
$changed_directories = @()
$changed_symlinks = @()
foreach ($file in $files) {
$filename = $file.dest
$local_checksum = $file.checksum
$filepath = Join-Path -Path $dest -ChildPath $filename
if (Test-Path -LiteralPath $filepath -PathType Leaf) {
if ($force) {
$checksum = Get-FileChecksum -path $filepath
if ($checksum -ne $local_checksum) {
$changed_files += $file
}
}
} elseif (Test-Path -LiteralPath $filepath -PathType Container) {
Fail-Json -obj $result -message "cannot copy file to dest '$filepath': object at path is already a directory"
} else {
$changed_files += $file
}
}
foreach ($directory in $directories) {
$dirname = $directory.dest
$dirpath = Join-Path -Path $dest -ChildPath $dirname
$parent_dir = [System.IO.Path]::GetDirectoryName($dirpath)
if (Test-Path -LiteralPath $parent_dir -PathType Leaf) {
Fail-Json -obj $result -message "cannot copy folder to dest '$dirpath': object at parent directory path is already a file"
}
if (Test-Path -LiteralPath $dirpath -PathType Leaf) {
Fail-Json -obj $result -message "cannot copy folder to dest '$dirpath': object at path is already a file"
} elseif (-not (Test-Path -LiteralPath $dirpath -PathType Container)) {
$changed_directories += $directory
}
}
# TODO: Handle symlinks
$result.files = $changed_files
$result.directories = $changed_directories
$result.symlinks = $changed_symlinks
} elseif ($copy_mode -eq "explode") {
# a single zip file containing the files and directories needs to be
# expanded this will always result in a change as the calculation is done
# on the win_copy action plugin and is only run if a change needs to occur
if (-not (Test-Path -LiteralPath $src -PathType Leaf)) {
Fail-Json -obj $result -message "Cannot expand src zip file: '$src' as it does not exist"
}
# Detect if the PS zip assemblies are available or whether to use Shell
$use_legacy = $false
try {
Add-Type -AssemblyName System.IO.Compression.FileSystem | Out-Null
Add-Type -AssemblyName System.IO.Compression | Out-Null
} catch {
$use_legacy = $true
}
if ($use_legacy) {
Extract-ZipLegacy -src $src -dest $dest
} else {
Extract-Zip -src $src -dest $dest
}
$result.changed = $true
} elseif ($copy_mode -eq "remote") {
# all copy actions are happening on the remote side (windows host), need
# too copy source and dest using PS code
$result.src = $src
$result.dest = $dest
if (-not (Test-Path -LiteralPath $src)) {
Fail-Json -obj $result -message "Cannot copy src file: '$src' as it does not exist"
}
if (Test-Path -LiteralPath $src -PathType Container) {
# we are copying a directory or the contents of a directory
$result.operation = 'folder_copy'
if ($src.EndsWith("/") -or $src.EndsWith("`\")) {
# copying the folder's contents to dest
$diff = ""
$child_files = Get-ChildItem -LiteralPath $src -Force
foreach ($child_file in $child_files) {
$dest_child_path = Join-Path -Path $dest -ChildPath $child_file.Name
if ($child_file.PSIsContainer) {
$diff += Copy-Folder -source $child_file.FullName -dest $dest_child_path
} else {
$diff += (Copy-File -source $child_file.FullName -dest $dest_child_path).diff
}
}
} else {
# copying the folder and it's contents to dest
$dest = Join-Path -Path $dest -ChildPath (Get-Item -LiteralPath $src -Force).Name
$result.dest = $dest
$diff = Copy-Folder -source $src -dest $dest
}
} else {
# we are just copying a single file to dest
$result.operation = 'file_copy'
$source_basename = (Get-Item -LiteralPath $src -Force).Name
$result.original_basename = $source_basename
if ($dest.EndsWith("/") -or $dest.EndsWith("`\")) {
$dest = Join-Path -Path $dest -ChildPath (Get-Item -LiteralPath $src -Force).Name
$result.dest = $dest
} else {
# check if the parent dir exists, this is only done if src is a
# file and dest if the path to a file (doesn't end with \ or /)
$parent_dir = Split-Path -LiteralPath $dest
if (Test-Path -LiteralPath $parent_dir -PathType Leaf) {
Fail-Json -obj $result -message "object at destination parent dir '$parent_dir' is currently a file"
} elseif (-not (Test-Path -LiteralPath $parent_dir -PathType Container)) {
Fail-Json -obj $result -message "Destination directory '$parent_dir' does not exist"
}
}
$copy_result = Copy-File -source $src -dest $dest
$diff = $copy_result.diff
$result.checksum = $copy_result.checksum
}
# the file might not exist if running in check mode
if (-not $check_mode -or (Test-Path -LiteralPath $dest -PathType Leaf)) {
$result.size = Get-FileSize -path $dest
} else {
$result.size = $null
}
if ($diff_mode) {
$result.diff.prepared = $diff
}
} elseif ($copy_mode -eq "single") {
# a single file is located in src and we need to copy to dest, this will
# always result in a change as the calculation is done on the Ansible side
# before this is run. This should also never run in check mode
if (-not (Test-Path -LiteralPath $src -PathType Leaf)) {
Fail-Json -obj $result -message "Cannot copy src file: '$src' as it does not exist"
}
# the dest parameter is a directory, we need to append original_basename
if ($dest.EndsWith("/") -or $dest.EndsWith("`\") -or (Test-Path -LiteralPath $dest -PathType Container)) {
$remote_dest = Join-Path -Path $dest -ChildPath $original_basename
$parent_dir = Split-Path -LiteralPath $remote_dest
# when dest ends with /, we need to create the destination directories
if (Test-Path -LiteralPath $parent_dir -PathType Leaf) {
Fail-Json -obj $result -message "object at destination parent dir '$parent_dir' is currently a file"
} elseif (-not (Test-Path -LiteralPath $parent_dir -PathType Container)) {
New-Item -Path $parent_dir -ItemType Directory | Out-Null
}
} else {
$remote_dest = $dest
$parent_dir = Split-Path -LiteralPath $remote_dest
# check if the dest parent dirs exist, need to fail if they don't
if (Test-Path -LiteralPath $parent_dir -PathType Leaf) {
Fail-Json -obj $result -message "object at destination parent dir '$parent_dir' is currently a file"
} elseif (-not (Test-Path -LiteralPath $parent_dir -PathType Container)) {
Fail-Json -obj $result -message "Destination directory '$parent_dir' does not exist"
}
}
if ($backup) {
$result.backup_file = Backup-File -path $remote_dest -WhatIf:$check_mode
}
Copy-Item -LiteralPath $src -Destination $remote_dest -Force | Out-Null
$result.changed = $true
}
Exit-Json -obj $result

@ -1,207 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Jon Hawkesworth (@jhawkesworth) <figs@unity.demon.co.uk>
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_copy
version_added: '1.9.2'
short_description: Copies files to remote locations on windows hosts
description:
- The C(win_copy) module copies a file on the local box to remote windows locations.
- For non-Windows targets, use the M(copy) module instead.
options:
content:
description:
- When used instead of C(src), sets the contents of a file directly to the
specified value.
- This is for simple values, for anything complex or with formatting please
switch to the M(template) module.
type: str
version_added: '2.3'
decrypt:
description:
- This option controls the autodecryption of source files using vault.
type: bool
default: yes
version_added: '2.5'
dest:
description:
- Remote absolute path where the file should be copied to.
- If C(src) is a directory, this must be a directory too.
- Use \ for path separators or \\ when in "double quotes".
- If C(dest) ends with \ then source or the contents of source will be
copied to the directory without renaming.
- If C(dest) is a nonexistent path, it will only be created if C(dest) ends
with "/" or "\", or C(src) is a directory.
- If C(src) and C(dest) are files and if the parent directory of C(dest)
doesn't exist, then the task will fail.
type: path
required: yes
backup:
description:
- Determine whether a backup should be created.
- When set to C(yes), create a backup file including the timestamp information
so you can get the original file back if you somehow clobbered it incorrectly.
- No backup is taken when C(remote_src=False) and multiple files are being
copied.
type: bool
default: no
version_added: '2.8'
force:
description:
- If set to C(yes), the file will only be transferred if the content
is different than destination.
- If set to C(no), the file will only be transferred if the
destination does not exist.
- If set to C(no), no checksuming of the content is performed which can
help improve performance on larger files.
type: bool
default: yes
version_added: '2.3'
local_follow:
description:
- This flag indicates that filesystem links in the source tree, if they
exist, should be followed.
type: bool
default: yes
version_added: '2.4'
remote_src:
description:
- If C(no), it will search for src at originating/master machine.
- If C(yes), it will go to the remote/target machine for the src.
type: bool
default: no
version_added: '2.3'
src:
description:
- Local path to a file to copy to the remote server; can be absolute or
relative.
- If path is a directory, it is copied (including the source folder name)
recursively to C(dest).
- If path is a directory and ends with "/", only the inside contents of
that directory are copied to the destination. Otherwise, if it does not
end with "/", the directory itself with all contents is copied.
- If path is a file and dest ends with "\", the file is copied to the
folder with the same filename.
- Required unless using C(content).
type: path
notes:
- Currently win_copy does not support copying symbolic links from both local to
remote and remote to remote.
- It is recommended that backslashes C(\) are used instead of C(/) when dealing
with remote paths.
- Because win_copy runs over WinRM, it is not a very efficient transfer
mechanism. If sending large files consider hosting them on a web service and
using M(win_get_url) instead.
seealso:
- module: assemble
- module: copy
- module: win_get_url
- module: win_robocopy
author:
- Jon Hawkesworth (@jhawkesworth)
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Copy a single file
win_copy:
src: /srv/myfiles/foo.conf
dest: C:\Temp\renamed-foo.conf
- name: Copy a single file, but keep a backup
win_copy:
src: /srv/myfiles/foo.conf
dest: C:\Temp\renamed-foo.conf
backup: yes
- name: Copy a single file keeping the filename
win_copy:
src: /src/myfiles/foo.conf
dest: C:\Temp\
- name: Copy folder to C:\Temp (results in C:\Temp\temp_files)
win_copy:
src: files/temp_files
dest: C:\Temp
- name: Copy folder contents recursively
win_copy:
src: files/temp_files/
dest: C:\Temp
- name: Copy a single file where the source is on the remote host
win_copy:
src: C:\Temp\foo.txt
dest: C:\ansible\foo.txt
remote_src: yes
- name: Copy a folder recursively where the source is on the remote host
win_copy:
src: C:\Temp
dest: C:\ansible
remote_src: yes
- name: Set the contents of a file
win_copy:
content: abc123
dest: C:\Temp\foo.txt
- name: Copy a single file as another user
win_copy:
src: NuGet.config
dest: '%AppData%\NuGet\NuGet.config'
vars:
ansible_become_user: user
ansible_become_password: pass
# The tmp dir must be set when using win_copy as another user
# This ensures the become user will have permissions for the operation
# Make sure to specify a folder both the ansible_user and the become_user have access to (i.e not %TEMP% which is user specific and requires Admin)
ansible_remote_tmp: 'c:\tmp'
'''
RETURN = r'''
backup_file:
description: Name of the backup file that was created.
returned: if backup=yes
type: str
sample: C:\Path\To\File.txt.11540.20150212-220915.bak
dest:
description: Destination file/path.
returned: changed
type: str
sample: C:\Temp\
src:
description: Source file used for the copy on the target machine.
returned: changed
type: str
sample: /home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source
checksum:
description: SHA1 checksum of the file after running copy.
returned: success, src is a file
type: str
sample: 6e642bb8dd5c2e027bf21dd923337cbb4214f827
size:
description: Size of the target, after execution.
returned: changed, src is a file
type: int
sample: 1220
operation:
description: Whether a single file copy took place or a folder copy.
returned: success
type: str
sample: file_copy
original_basename:
description: Basename of the copied file.
returned: changed, src is a file
type: str
sample: foo.txt
'''

@ -1,360 +0,0 @@
#!powershell
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
Set-StrictMode -Version 2
$ErrorActionPreference = "Stop"
$ConfirmPreference = "None"
Set-Variable -Visibility Public -Option ReadOnly,AllScope,Constant -Name "AddressFamilies" -Value @{
[System.Net.Sockets.AddressFamily]::InterNetworkV6 = 'IPv6'
[System.Net.Sockets.AddressFamily]::InterNetwork = 'IPv4'
}
$result = @{changed=$false}
$params = Parse-Args -arguments $args -supports_check_mode $true
Set-Variable -Visibility Public -Option ReadOnly,AllScope,Constant -Name "log_path" -Value (
Get-AnsibleParam $params "log_path"
)
$adapter_names = Get-AnsibleParam $params "adapter_names" -Default "*"
$dns_servers = Get-AnsibleParam $params "dns_servers" -aliases "ipv4_addresses","ip_addresses","addresses" -FailIfEmpty $result
$check_mode = Get-AnsibleParam $params "_ansible_check_mode" -Default $false
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
}
}
Function Get-OptionalProperty {
<#
.SYNOPSIS
Retreives a property that may not exist from an object that may be null.
Optionally returns a default value.
Optionally coalesces to a new type with -as.
May return null, but will not throw.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true)]
[Object]
$InputObject ,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[String]
$Name ,
[Parameter()]
[AllowNull()]
[Object]
$Default ,
[Parameter()]
[System.Type]
$As
)
Process {
if ($null -eq $InputObject) {
return $null
}
$value = if ($InputObject.PSObject.Properties.Name -contains $Name) {
$InputObject.$Name
} else {
$Default
}
if ($As) {
return $value -as $As
}
return $value
}
}
Function Get-NetAdapterInfo {
[CmdletBinding()]
Param (
[Parameter(ValueFromPipeline=$true)]
[String]$Name = "*"
)
Process {
if (Get-Command -Name Get-NetAdapter -ErrorAction SilentlyContinue) {
$adapter_info = Get-NetAdapter @PSBoundParameters | Select-Object -Property Name, InterfaceIndex
} else {
# Older hosts 2008/2008R2 don't have Get-NetAdapter, fallback to deprecated Win32_NetworkAdapter
$cim_params = @{
ClassName = "Win32_NetworkAdapter"
Property = "InterfaceIndex", "NetConnectionID"
}
if ($Name.Contains("*")) {
$cim_params.Filter = "NetConnectionID LIKE '$($Name.Replace("*", "%"))'"
} else {
$cim_params.Filter = "NetConnectionID = '$Name'"
}
$adapter_info = Get-CimInstance @cim_params | Select-Object -Property @(
@{Name="Name"; Expression={$_.NetConnectionID}},
@{Name="InterfaceIndex"; Expression={$_.InterfaceIndex}}
)
}
# Need to filter the adapter that are not IPEnabled, while we are at it, also get the DNS config.
$net_info = $adapter_info | ForEach-Object -Process {
$cim_params = @{
ClassName = "Win32_NetworkAdapterConfiguration"
Filter = "InterfaceIndex = $($_.InterfaceIndex)"
Property = "DNSServerSearchOrder", "IPEnabled", "SettingID"
}
$adapter_config = Get-CimInstance @cim_params |
Select-Object -Property DNSServerSearchOrder, IPEnabled, @{
Name = 'InterfaceGuid'
Expression = { $_.SettingID }
}
if ($adapter_config.IPEnabled -eq $false) {
return
}
$reg_info = $adapter_config | Get-RegistryNameServerInfo
[PSCustomObject]@{
Name = $_.Name
InterfaceIndex = $_.InterfaceIndex
InterfaceGuid = $adapter_config.InterfaceGuid
RegInfo = $reg_info
}
}
if (@($net_info).Count -eq 0 -and -not $Name.Contains("*")) {
throw "Get-NetAdapterInfo: Failed to find network adapter(s) that are IP enabled with the name '$Name'"
}
$net_info
}
}
Function Get-RegistryNameServerInfo {
[CmdletBinding()]
Param (
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
[System.Guid]
$InterfaceGuid
)
Begin {
$protoItems = @{
[System.Net.Sockets.AddressFamily]::InterNetwork = @{
Interface = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{{{0}}}'
StaticNameServer = 'NameServer'
DhcpNameServer = 'DhcpNameServer'
EnableDhcp = 'EnableDHCP'
}
[System.Net.Sockets.AddressFamily]::InterNetworkV6 = @{
Interface = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces\{{{0}}}'
StaticNameServer = 'NameServer'
DhcpNameServer = 'Dhcpv6DNSServers'
EnableDhcp = 'EnableDHCP'
}
}
}
Process {
foreach ($addrFamily in $AddressFamilies.Keys) {
$items = $protoItems[$addrFamily]
$regPath = $items.Interface -f $InterfaceGuid
if (($iface = Get-Item -LiteralPath $regPath -ErrorAction Ignore)) {
$iprop = $iface | Get-ItemProperty
$famInfo = @{
AddressFamily = $addrFamily
UsingDhcp = Get-OptionalProperty -InputObject $iprop -Name $items.EnableDhcp -As bool
EffectiveNameServers = @()
DhcpAssignedNameServers = @()
NameServerBadFormat = $false
}
if (($ns = Get-OptionalProperty -InputObject $iprop -Name $items.DhcpNameServer)) {
$famInfo.EffectiveNameServers = $famInfo.DhcpAssignedNameServers = $ns.Split(' ')
}
if (($ns = Get-OptionalProperty -InputObject $iprop -Name $items.StaticNameServer)) {
$famInfo.EffectiveNameServers = $famInfo.StaticNameServers = $ns -split '[,;\ ]'
$famInfo.UsingDhcp = $false
$famInfo.NameServerBadFormat = $ns -match '[;\ ]'
}
$famInfo
}
}
}
}
# minimal impl of Set-DnsClientServerAddress for 2008/2008R2
Function Set-DnsClientServerAddressLegacy {
Param(
[int]$InterfaceIndex,
[Array]$ServerAddresses=@(),
[switch]$ResetServerAddresses
)
$cim_params = @{
ClassName = "Win32_NetworkAdapterConfiguration"
Filter = "InterfaceIndex = $InterfaceIndex"
KeyOnly = $true
}
$adapter_config = Get-CimInstance @cim_params
If($ResetServerAddresses) {
$arguments = @{}
}
Else {
$arguments = @{ DNSServerSearchOrder = [string[]]$ServerAddresses }
}
$res = Invoke-CimMethod -InputObject $adapter_config -MethodName SetDNSServerSearchOrder -Arguments $arguments
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 Test-DnsClientMatch {
Param(
[PSCustomObject]$AdapterInfo,
[System.Net.IPAddress[]] $dns_servers
)
Write-DebugLog ("Getting DNS config for adapter {0}" -f $AdapterInfo.Name)
foreach ($proto in $AdapterInfo.RegInfo) {
$desired_dns = if ($dns_servers) {
$dns_servers | Where-Object -FilterScript {$_.AddressFamily -eq $proto.AddressFamily}
}
$current_dns = [System.Net.IPAddress[]]($proto.EffectiveNameServers)
Write-DebugLog ("Current DNS settings for '{1}' Address Family: {0}" -f ([string[]]$current_dns -join ", "),$AddressFamilies[$proto.AddressFamily])
if ($proto.NameServerBadFormat) {
Write-DebugLog "Malicious DNS server format detected. Will set DNS desired state."
return $false
# See: https://www.welivesecurity.com/2016/06/02/crouching-tiger-hidden-dns/
}
if ($proto.UsingDhcp -and -not $desired_dns) {
Write-DebugLog "DHCP DNS Servers are in use and no DNS servers were requested (DHCP is desired)."
} else {
if ($desired_dns -and -not $current_dns) {
Write-DebugLog "There are currently no DNS servers in use, but they should be present."
return $false
}
if ($current_dns -and -not $desired_dns) {
Write-DebugLog "There are currently DNS servers in use, but they should be absent."
return $false
}
if ($null -ne $current_dns -and
$null -ne $desired_dns -and
(Compare-Object -ReferenceObject $current_dns -DifferenceObject $desired_dns -SyncWindow 0)) {
Write-DebugLog "Static DNS servers are not in the desired state (incorrect or in the wrong order)."
return $false
}
}
Write-DebugLog ("Current DNS settings match ({0})." -f ([string[]]$desired_dns -join ", "))
}
return $true
}
Function Assert-IPAddress {
Param([string] $address)
$addrout = $null
return [System.Net.IPAddress]::TryParse($address, [ref] $addrout)
}
Function Set-DnsClientAddresses
{
Param(
[PSCustomObject]$AdapterInfo,
[System.Net.IPAddress[]] $dns_servers
)
Write-DebugLog ("Setting DNS addresses for adapter {0} to ({1})" -f $AdapterInfo.Name, ([string[]]$dns_servers -join ", "))
If ($dns_servers) {
Set-DnsClientServerAddress -InterfaceIndex $AdapterInfo.InterfaceIndex -ServerAddresses $dns_servers
} Else {
Set-DnsClientServerAddress -InterfaceIndex $AdapterInfo.InterfaceIndex -ResetServerAddress
}
}
if($dns_servers -is [string]) {
if($dns_servers.Length -gt 0) {
$dns_servers = @($dns_servers)
} else {
$dns_servers = @()
}
}
# Using object equals here, to check for exact match (without implicit type conversion)
if([System.Object]::Equals($adapter_names, "*")) {
$adapters = Get-NetAdapterInfo
} else {
$adapters = $adapter_names | Get-NetAdapterInfo
}
Try {
Write-DebugLog ("Validating IP addresses ({0})" -f ($dns_servers -join ", "))
$invalid_addresses = @($dns_servers | Where-Object { -not (Assert-IPAddress $_) })
if($invalid_addresses.Count -gt 0) {
throw "Invalid IP address(es): ({0})" -f ($invalid_addresses -join ", ")
}
foreach($adapter_info in $adapters) {
Write-DebugLog ("Validating adapter name {0}" -f $adapter_info.Name)
if(-not (Test-DnsClientMatch $adapter_info $dns_servers)) {
$result.changed = $true
if(-not $check_mode) {
Set-DnsClientAddresses $adapter_info $dns_servers
} else {
Write-DebugLog "Check mode, skipping"
}
}
}
Exit-Json $result
}
Catch {
$excep = $_
Write-DebugLog "Exception: $($excep | out-string)"
Throw
}

@ -1,72 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Red Hat, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
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 the InterfaceAlias of C(Get-DnsClientServerAddress).
type: list
required: yes
dns_servers:
description:
- Single or ordered list of DNS servers (IPv4 and IPv6 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.
- IPv6 DNS servers can only be set on Windows Server 2012 or newer, older hosts can only set IPv4 addresses.
- Before 2.10 use ipv4_addresses instead.
type: list
required: yes
aliases: [ "ipv4_addresses", "ip_addresses", "addresses" ]
notes:
- Before 2.10, when setting an empty list of DNS server addresses on an adapter with DHCP enabled, a change was always registered.
- In 2.10, DNS servers will always be reset if the format of nameservers in the registry is not comma delimited.
See U(https://www.welivesecurity.com/2016/06/02/crouching-tiger-hidden-dns/)
author:
- Matt Davis (@nitzmahone)
- Brian Scholer (@briantist)
'''
EXAMPLES = r'''
- name: Set a single address on the adapter named Ethernet
win_dns_client:
adapter_names: Ethernet
dns_servers: 192.168.34.5
- name: 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: '*'
dns_servers:
- 192.168.34.5
- 192.168.34.6
log_path: C:\dns_log.txt
- name: Set IPv6 DNS servers on the adapter named Ethernet
win_dns_client:
adapter_names: Ethernet
dns_servers:
- '2001:db8::2'
- '2001:db8::3'
- name: Configure all adapters whose names begin with Ethernet to use DHCP-assigned DNS values
win_dns_client:
adapter_names: 'Ethernet*'
dns_servers: []
'''
RETURN = r'''
'''

@ -1,160 +0,0 @@
#!powershell
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
Set-StrictMode -Version 2
$ErrorActionPreference = "Stop"
# FUTURE: Consider action wrapper to manage reboots and credential changes
Function Ensure-Prereqs {
$gwf = Get-WindowsFeature AD-Domain-Services
if ($gwf.InstallState -ne "Installed") {
$result.changed = $true
# NOTE: AD-Domain-Services includes: RSAT-AD-AdminCenter, RSAT-AD-Powershell and RSAT-ADDS-Tools
$awf = Add-WindowsFeature AD-Domain-Services -WhatIf:$check_mode
$result.reboot_required = $awf.RestartNeeded
# FUTURE: Check if reboot necessary
return $true
}
return $false
}
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false
$dns_domain_name = Get-AnsibleParam -obj $params -name "dns_domain_name" -failifempty $true
$domain_netbios_name = Get-AnsibleParam -obj $params -name "domain_netbios_name"
$safe_mode_admin_password = Get-AnsibleParam -obj $params -name "safe_mode_password" -failifempty $true
$database_path = Get-AnsibleParam -obj $params -name "database_path" -type "path"
$sysvol_path = Get-AnsibleParam -obj $params -name "sysvol_path" -type "path"
$log_path = Get-AnsibleParam -obj $params -name "log_path" -type "path"
$create_dns_delegation = Get-AnsibleParam -obj $params -name "create_dns_delegation" -type "bool"
$domain_mode = Get-AnsibleParam -obj $params -name "domain_mode" -type "str"
$forest_mode = Get-AnsibleParam -obj $params -name "forest_mode" -type "str"
$install_dns = Get-AnsibleParam -obj $params -name "install_dns" -type "bool" -default $true
# FUTURE: Support down to Server 2012?
if ([System.Environment]::OSVersion.Version -lt [Version]"6.3.9600.0") {
Fail-Json -message "win_domain requires Windows Server 2012R2 or higher"
}
# Check that domain_netbios_name is less than 15 characters
if ($domain_netbios_name -and $domain_netbios_name.length -gt 15) {
Fail-Json -message "The parameter 'domain_netbios_name' should not exceed 15 characters in length"
}
$result = @{
changed=$false;
reboot_required=$false;
}
# FUTURE: Any sane way to do the detection under check-mode *without* installing the feature?
$installed = Ensure-Prereqs
# when in check mode and the prereq was "installed" we need to exit early as
# the AD cmdlets weren't really installed
if ($check_mode -and $installed) {
Exit-Json -obj $result
}
# Check that we got a valid domain_mode
$valid_domain_modes = [Enum]::GetNames((Get-Command -Name Install-ADDSForest).Parameters.DomainMode.ParameterType)
if (($null -ne $domain_mode) -and -not ($domain_mode -in $valid_domain_modes)) {
Fail-Json -obj $result -message "The parameter 'domain_mode' does not accept '$domain_mode', please use one of: $valid_domain_modes"
}
# Check that we got a valid forest_mode
$valid_forest_modes = [Enum]::GetNames((Get-Command -Name Install-ADDSForest).Parameters.ForestMode.ParameterType)
if (($null -ne $forest_mode) -and -not ($forest_mode -in $valid_forest_modes)) {
Fail-Json -obj $result -message "The parameter 'forest_mode' does not accept '$forest_mode', please use one of: $valid_forest_modes"
}
$forest = $null
try {
# Cannot use Get-ADForest as that requires credential delegation, the below does not
$forest_context = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext -ArgumentList Forest, $dns_domain_name
$forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($forest_context)
} catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] {
} catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException] { }
if (-not $forest) {
$result.changed = $true
$sm_cred = ConvertTo-SecureString $safe_mode_admin_password -AsPlainText -Force
$install_params = @{
DomainName=$dns_domain_name;
SafeModeAdministratorPassword=$sm_cred;
Confirm=$false;
SkipPreChecks=$true;
InstallDns=$install_dns;
NoRebootOnCompletion=$true;
WhatIf=$check_mode;
}
if ($database_path) {
$install_params.DatabasePath = $database_path
}
if ($sysvol_path) {
$install_params.SysvolPath = $sysvol_path
}
if ($log_path) {
$install_params.LogPath = $log_path
}
if ($domain_netbios_name) {
$install_params.DomainNetBiosName = $domain_netbios_name
}
if ($null -ne $create_dns_delegation) {
$install_params.CreateDnsDelegation = $create_dns_delegation
}
if ($domain_mode) {
$install_params.DomainMode = $domain_mode
}
if ($forest_mode) {
$install_params.ForestMode = $forest_mode
}
$iaf = $null
try {
$iaf = Install-ADDSForest @install_params
} catch [Microsoft.DirectoryServices.Deployment.DCPromoExecutionException] {
# ExitCode 15 == 'Role change is in progress or this computer needs to be restarted.'
# DCPromo exit codes details can be found at https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/deploy/troubleshooting-domain-controller-deployment
if ($_.Exception.ExitCode -in @(15, 19)) {
$result.reboot_required = $true
} else {
Fail-Json -obj $result -message "Failed to install ADDSForest, DCPromo exited with $($_.Exception.ExitCode): $($_.Exception.Message)"
}
}
if ($check_mode) {
# the return value after -WhatIf does not have RebootRequired populated
# manually set to True as the domain would have been installed
$result.reboot_required = $true
} elseif ($null -ne $iaf) {
$result.reboot_required = $iaf.RebootRequired
# The Netlogon service is set to auto start but is not started. This is
# required for Ansible to connect back to the host and reboot in a
# later task. Even if this fails Ansible can still connect but only
# with ansible_winrm_transport=basic so we just display a warning if
# this fails.
try {
Start-Service -Name Netlogon
} catch {
Add-Warning -obj $result -message "Failed to start the Netlogon service after promoting the host, Ansible may be unable to connect until the host is manually rebooting: $($_.Exception.Message)"
}
}
}
Exit-Json $result

@ -1,121 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Red Hat, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
module: win_domain
short_description: Ensures the existence of a Windows domain
version_added: 2.3
description:
- Ensure that the domain named by C(dns_domain_name) exists and is reachable.
- If the domain is not reachable, the domain is created in a new forest on the target Windows Server 2012R2+ host.
- This module may require subsequent use of the M(win_reboot) action if changes are made.
options:
dns_domain_name:
description:
- The DNS name of the domain which should exist and be reachable or reside on the target Windows host.
type: str
required: yes
domain_netbios_name:
description:
- The NetBIOS name for the root domain in the new forest.
- For NetBIOS names to be valid for use with this parameter they must be single label names of 15 characters or less, if not it will fail.
- If this parameter is not set, then the default is automatically computed from the value of the I(domain_name) parameter.
type: str
version_added: '2.6'
safe_mode_password:
description:
- Safe mode password for the domain controller.
type: str
required: yes
database_path:
description:
- The path to a directory on a fixed disk of the Windows host where the
domain database will be created.
- If not set then the default path is C(%SYSTEMROOT%\NTDS).
type: path
version_added: '2.5'
log_path:
description:
- Specifies the fully qualified, non-UNC path to a directory on a fixed disk of the local computer where the log file for this operation is written.
- If not set then the default path is C(%SYSTEMROOT%\NTDS).
type: path
version_added: '2.10'
sysvol_path:
description:
- The path to a directory on a fixed disk of the Windows host where the
Sysvol file will be created.
- If not set then the default path is C(%SYSTEMROOT%\SYSVOL).
type: path
version_added: '2.5'
create_dns_delegation:
description:
- Whether to create a DNS delegation that references the new DNS server that you install along with the domain controller.
- Valid for Active Directory-integrated DNS only.
- The default is computed automatically based on the environment.
type: bool
version_added: '2.8'
domain_mode:
description:
- Specifies the domain functional level of the first domain in the creation of a new forest.
- The domain functional level cannot be lower than the forest functional level, but it can be higher.
- The default is automatically computed and set.
type: str
choices: [ Win2003, Win2008, Win2008R2, Win2012, Win2012R2, WinThreshold ]
version_added: '2.8'
forest_mode:
description:
- Specifies the forest functional level for the new forest.
- The default forest functional level in Windows Server is typically the same as the version you are running.
# - Beware that the default forest functional level in Windows Server 2008 R2 when you create a new forest is C(Win2003).
type: str
choices: [ Win2003, Win2008, Win2008R2, Win2012, Win2012R2, WinThreshold ]
version_added: '2.8'
install_dns:
description:
- Whether to install the DNS service when creating the domain controller.
type: bool
default: yes
version_added: '2.10'
seealso:
- module: win_domain_controller
- module: win_domain_computer
- module: win_domain_group
- module: win_domain_membership
- module: win_domain_user
author:
- Matt Davis (@nitzmahone)
'''
RETURN = r'''
reboot_required:
description: True if changes were made that require a reboot.
returned: always
type: bool
sample: true
'''
EXAMPLES = r'''
- name: Create new domain in a new forest on the target host
win_domain:
dns_domain_name: ansible.vagrant
safe_mode_password: password123!
- name: Create new Windows domain in a new forest with specific parameters
win_domain:
create_dns_delegation: no
database_path: C:\Windows\NTDS
dns_domain_name: ansible.vagrant
domain_mode: Win2012R2
domain_netbios_name: ANSIBLE
forest_mode: Win2012R2
safe_mode_password: password123!
sysvol_path: C:\Windows\SYSVOL
register: domain_install
'''

@ -1,299 +0,0 @@
#!powershell
# Copyright: (c) 2017, Red Hat, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
Set-StrictMode -Version 2
$ErrorActionPreference = "Stop"
$ConfirmPreference = "None"
$log_path = $null
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
}
}
$required_features = @("AD-Domain-Services","RSAT-ADDS")
Function Get-MissingFeatures {
Write-DebugLog "Checking for missing Windows features..."
$features = @(Get-WindowsFeature $required_features)
If($features.Count -ne $required_features.Count) {
Throw "One or more Windows features required for a domain controller are unavailable"
}
$missing_features = @($features | Where-Object InstallState -ne Installed)
return ,$missing_features # no, the comma's not a typo- allows us to return an empty array
}
Function Ensure-FeatureInstallation {
# ensure RSAT-ADDS and AD-Domain-Services features are installed
Write-DebugLog "Ensuring required Windows features are installed..."
$feature_result = Install-WindowsFeature $required_features
$result.reboot_required = $feature_result.RestartNeeded
If(-not $feature_result.Success) {
Exit-Json -message ("Error installing AD-Domain-Services and RSAT-ADDS features: {0}" -f ($feature_result | Out-String))
}
}
# return the domain we're a DC for, or null if not a DC
Function Get-DomainControllerDomain {
Write-DebugLog "Checking for domain controller role and domain name"
$sys_cim = Get-CIMInstance Win32_ComputerSystem
$is_dc = $sys_cim.DomainRole -in (4,5) # backup/primary DC
# this will be our workgroup or joined-domain if we're not a DC
$domain = $sys_cim.Domain
Switch($is_dc) {
$true { return $domain }
Default { return $null }
}
}
Function Create-Credential {
Param(
[string] $cred_user,
[string] $cred_password
)
$cred = New-Object System.Management.Automation.PSCredential($cred_user, $($cred_password | ConvertTo-SecureString -AsPlainText -Force))
Return $cred
}
Function Get-OperationMasterRoles {
$assigned_roles = @((Get-ADDomainController -Server localhost).OperationMasterRoles)
Return ,$assigned_roles # no, the comma's not a typo- allows us to return an empty array
}
$result = @{
changed = $false
reboot_required = $false
}
$params = Parse-Args -arguments $args -supports_check_mode $true
$dns_domain_name = Get-AnsibleParam -obj $params -name "dns_domain_name"
$safe_mode_password= Get-AnsibleParam -obj $params -name "safe_mode_password"
$domain_admin_user = Get-AnsibleParam -obj $params -name "domain_admin_user" -failifempty $result
$domain_admin_password= Get-AnsibleParam -obj $params -name "domain_admin_password" -failifempty $result
$local_admin_password= Get-AnsibleParam -obj $params -name "local_admin_password"
$database_path = Get-AnsibleParam -obj $params -name "database_path" -type "path"
$sysvol_path = Get-AnsibleParam -obj $params -name "sysvol_path" -type "path"
$domain_log_path = Get-AnsibleParam -obj $params -name "domain_log_path" -type "path" # TODO: Use log_path and alias domain_log_path once the log_path for debug logging option has been removed.
$read_only = Get-AnsibleParam -obj $params -name "read_only" -type "bool" -default $false
$site_name = Get-AnsibleParam -obj $params -name "site_name" -type "str" -failifempty $read_only
$install_dns = Get-AnsibleParam -obj $params -name "install_dns" -type "bool"
$state = Get-AnsibleParam -obj $params -name "state" -validateset ("domain_controller", "member_server") -failifempty $result
$log_path = Get-AnsibleParam -obj $params -name "log_path"
if ($log_path) {
$msg = "Param 'log_path' is deprecated. See the module docs for more information"
Add-DeprecationWarning -obj $result -message $msg -version "2.14"
}
$_ansible_check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false
$global:log_path = $log_path
Try {
# ensure target OS support; < 2012 doesn't have cmdlet support for DC promotion
If(-not (Get-Command Install-WindowsFeature -ErrorAction SilentlyContinue)) {
Fail-Json -message "win_domain_controller requires at least Windows Server 2012"
}
# validate args
If($state -eq "domain_controller") {
If(-not $dns_domain_name) {
Fail-Json -message "dns_domain_name is required when desired state is 'domain_controller'"
}
If(-not $safe_mode_password) {
Fail-Json -message "safe_mode_password is required when desired state is 'domain_controller'"
}
# ensure that domain admin user is in UPN or down-level domain format (prevent hang from https://support.microsoft.com/en-us/kb/2737935)
If(-not $domain_admin_user.Contains("\") -and -not $domain_admin_user.Contains("@")) {
Fail-Json -message "domain_admin_user must be in domain\user or user@domain.com format"
}
}
Else { # member_server
If(-not $local_admin_password) {
Fail-Json -message "local_admin_password is required when desired state is 'member_server'"
}
}
# short-circuit "member server" check, since we don't need feature checks for this...
$current_dc_domain = Get-DomainControllerDomain
If($state -eq "member_server" -and -not $current_dc_domain) {
Exit-Json $result
}
# all other operations will require the AD-DS and RSAT-ADDS features...
$missing_features = Get-MissingFeatures
If($missing_features.Count -gt 0) {
Write-DebugLog ("Missing Windows features ({0}), need to install" -f ($missing_features -join ", "))
$result.changed = $true # we need to install features
If($_ansible_check_mode) {
# bail out here- we can't proceed without knowing the features are installed
Write-DebugLog "check-mode, exiting early"
Exit-Json $result
}
Ensure-FeatureInstallation | Out-Null
}
$domain_admin_cred = Create-Credential -cred_user $domain_admin_user -cred_password $domain_admin_password
switch($state) {
domain_controller {
If(-not $safe_mode_password) {
Fail-Json -message "safe_mode_password is required for state=domain_controller"
}
If($current_dc_domain) {
# FUTURE: implement managed Remove/Add to change domains?
If($current_dc_domain -ne $dns_domain_name) {
Fail-Json "$(hostname) is a domain controller for domain $current_dc_domain; changing DC domains is not implemented"
}
}
# need to promote to DC
If(-not $current_dc_domain) {
Write-DebugLog "Not currently a domain controller; needs promotion"
$result.changed = $true
If($_ansible_check_mode) {
Write-DebugLog "check-mode, exiting early"
Fail-Json -message $result
}
$result.reboot_required = $true
$safe_mode_secure = $safe_mode_password | ConvertTo-SecureString -AsPlainText -Force
Write-DebugLog "Installing domain controller..."
$install_params = @{
DomainName = $dns_domain_name
Credential = $domain_admin_cred
SafeModeAdministratorPassword = $safe_mode_secure
}
if ($database_path) {
$install_params.DatabasePath = $database_path
}
if ($domain_log_path) {
$install_params.LogPath = $domain_log_path
}
if ($sysvol_path) {
$install_params.SysvolPath = $sysvol_path
}
if ($read_only) {
# while this is a switch value, if we set on $false site_name is required
# https://github.com/ansible/ansible/issues/35858
$install_params.ReadOnlyReplica = $true
}
if ($site_name) {
$install_params.SiteName = $site_name
}
if ($null -ne $install_dns) {
$install_params.InstallDns = $install_dns
}
try
{
$null = Install-ADDSDomainController -NoRebootOnCompletion -Force @install_params
} catch [Microsoft.DirectoryServices.Deployment.DCPromoExecutionException] {
# ExitCode 15 == 'Role change is in progress or this computer needs to be restarted.'
# DCPromo exit codes details can be found at https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/deploy/troubleshooting-domain-controller-deployment
if ($_.Exception.ExitCode -eq 15) {
$result.reboot_required = $true
} else {
Fail-Json -obj $result -message "Failed to install ADDSDomainController with DCPromo: $($_.Exception.Message)"
}
}
# If $_.FullyQualifiedErrorId -eq 'Test.VerifyUserCredentialPermissions.DCPromo.General.25,Microsoft.DirectoryServices.Deployment.PowerShell.Commands.InstallADDSDomainControllerCommand'
# the module failed to resolve the given dns domain name
Write-DebugLog "Installation complete, trying to start the Netlogon service"
# The Netlogon service is set to auto start but is not started. This is
# required for Ansible to connect back to the host and reboot in a
# later task. Even if this fails Ansible can still connect but only
# with ansible_winrm_transport=basic so we just display a warning if
# this fails.
try {
Start-Service -Name Netlogon
} catch {
Write-DebugLog "Failed to start the Netlogon service: $($_.Exception.Message)"
Add-Warning -obj $result -message "Failed to start the Netlogon service after promoting the host, Ansible may be unable to connect until the host is manually rebooting: $($_.Exception.Message)"
}
Write-DebugLog "Domain Controller setup completed, needs reboot..."
}
}
member_server {
If(-not $local_admin_password) {
Fail-Json -message "local_admin_password is required for state=domain_controller"
}
# at this point we already know we're a DC and shouldn't be...
Write-DebugLog "Need to uninstall domain controller..."
$result.changed = $true
Write-DebugLog "Checking for operation master roles assigned to this DC..."
$assigned_roles = Get-OperationMasterRoles
# FUTURE: figure out a sane way to hand off roles automatically (designated recipient server, randomly look one up?)
If($assigned_roles.Count -gt 0) {
Fail-Json -message ("This domain controller has operation master role(s) ({0}) assigned; they must be moved to other DCs before demotion (see Move-ADDirectoryServerOperationMasterRole)" -f ($assigned_roles -join ", "))
}
If($_ansible_check_mode) {
Write-DebugLog "check-mode, exiting early"
Exit-Json $result
}
$result.reboot_required = $true
$local_admin_secure = $local_admin_password | ConvertTo-SecureString -AsPlainText -Force
Write-DebugLog "Uninstalling domain controller..."
Uninstall-ADDSDomainController -NoRebootOnCompletion -LocalAdministratorPassword $local_admin_secure -Credential $domain_admin_cred
Write-DebugLog "Uninstallation complete, needs reboot..."
}
default { throw ("invalid state {0}" -f $state) }
}
Exit-Json $result
}
Catch {
$excep = $_
Write-DebugLog "Exception: $($excep | out-string)"
Throw
}

@ -1,156 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Red Hat, Inc.
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
module: win_domain_controller
short_description: Manage domain controller/member server state for a Windows host
version_added: '2.3'
description:
- Ensure that a Windows Server 2012+ host is configured as a domain controller or demoted to member server.
- 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_controller), the DNS name of the domain for which the targeted Windows host should be a DC.
type: str
domain_admin_user:
description:
- Username of a domain admin for the target domain (necessary to promote or demote a domain controller).
type: str
required: true
domain_admin_password:
description:
- Password for the specified C(domain_admin_user).
type: str
required: true
safe_mode_password:
description:
- Safe mode password for the domain controller (required when C(state) is C(domain_controller)).
type: str
local_admin_password:
description:
- Password to be assigned to the local C(Administrator) user (required when C(state) is C(member_server)).
type: str
read_only:
description:
- Whether to install the domain controller as a read only replica for an existing domain.
type: bool
default: no
version_added: '2.5'
site_name:
description:
- Specifies the name of an existing site where you can place the new domain controller.
- This option is required when I(read_only) is C(yes).
type: str
version_added: '2.5'
state:
description:
- Whether the target host should be a domain controller or a member server.
type: str
choices: [ domain_controller, member_server ]
database_path:
description:
- The path to a directory on a fixed disk of the Windows host where the
domain database will be created..
- If not set then the default path is C(%SYSTEMROOT%\NTDS).
type: path
version_added: '2.5'
domain_log_path:
description:
- Specified the fully qualified, non-UNC path to a directory on a fixed disk of the local computer that will
contain the domain log files.
type: path
version_added: '2.10'
sysvol_path:
description:
- The path to a directory on a fixed disk of the Windows host where the
Sysvol folder will be created.
- If not set then the default path is C(%SYSTEMROOT%\SYSVOL).
type: path
version_added: '2.5'
install_dns:
description:
- Whether to install the DNS service when creating the domain controller.
- If not specified then the C(-InstallDns) option is not supplied to C(Install-ADDSDomainController) command,
see U(https://docs.microsoft.com/en-us/powershell/module/addsdeployment/install-addsdomaincontroller).
type: bool
version_added: '2.10'
log_path:
description:
- The path to log any debug information when running the module.
- This option is deprecated and should not be used, it will be removed in Ansible 2.14.
- This does not relate to the C(-LogPath) paramter of the install controller cmdlet.
type: str
seealso:
- module: win_domain
- module: win_domain_computer
- module: win_domain_group
- module: win_domain_membership
- module: win_domain_user
author:
- Matt Davis (@nitzmahone)
'''
RETURN = r'''
reboot_required:
description: True if changes were made that require a reboot.
returned: always
type: bool
sample: true
'''
EXAMPLES = r'''
- name: Ensure a server is a domain controller
win_domain_controller:
dns_domain_name: ansible.vagrant
domain_admin_user: testguy@ansible.vagrant
domain_admin_password: password123!
safe_mode_password: password123!
state: domain_controller
# ensure a server is not a domain controller
# note that without an action wrapper, in the case where a DC is demoted,
# the task will fail with a 401 Unauthorized, because the domain credential
# becomes invalid to fetch the final output over WinRM. This requires win_async
# with credential switching (or other clever credential-switching
# mechanism to get the output and trigger the required reboot)
- win_domain_controller:
domain_admin_user: testguy@ansible.vagrant
domain_admin_password: password123!
local_admin_password: password123!
state: member_server
- name: Promote server as a read only domain controller
win_domain_controller:
dns_domain_name: ansible.vagrant
domain_admin_user: testguy@ansible.vagrant
domain_admin_password: password123!
safe_mode_password: password123!
state: domain_controller
read_only: yes
site_name: London
- name: Promote server with custom paths
win_domain_controller:
dns_domain_name: ansible.vagrant
domain_admin_user: testguy@ansible.vagrant
domain_admin_password: password123!
safe_mode_password: password123!
state: domain_controller
sysvol_path: D:\SYSVOL
database_path: D:\NTDS
domain_log_path: D:\NTDS
register: dc_promotion
- name: Reboot after promotion
win_reboot:
when: dc_promotion.reboot_required
'''

@ -1,315 +0,0 @@
#!powershell
# Copyright: (c) 2017, Red Hat, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
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.Security.Authentication.AuthenticationException] {
Write-DebugLog "Failed to get computer domain. Attempting a different method."
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$user_principal = [System.DirectoryServices.AccountManagement.UserPrincipal]::Current
If ($user_principal.ContextType -eq "Machine") {
$current_dns_domain = (Get-CimInstance -ClassName Win32_ComputerSystem -Property Domain).Domain
$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
}
Else {
Fail-Json -obj $result -message "Failed to authenticate with domain controller and cannot retrieve the existing domain name: $($_.Exception.Message)"
}
}
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-CIMInstance Win32_ComputerSystem).PartOfDomain
}
Function Join-Domain {
Param(
[string] $dns_domain_name,
[string] $new_hostname,
[string] $domain_admin_user,
[string] $domain_admin_password,
[string] $domain_ou_path
)
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
}
if($domain_ou_path){
Write-DebugLog "adding OU destination arg to Add-Computer args"
$add_args["OUPath"] = $domain_ou_path
}
$argstr = $add_args | Out-String
Write-DebugLog "calling Add-Computer with args: $argstr"
try {
$add_result = Add-Computer @add_args
} catch {
Fail-Json -obj $result -message "failed to join domain: $($_.Exception.Message)"
}
Write-DebugLog ("Add-Computer result was \n{0}" -f $add_result | Out-String)
}
Function Get-Workgroup {
return (Get-CIMInstance Win32_ComputerSystem).Workgroup
}
Function Set-Workgroup {
Param(
[string] $workgroup_name
)
Write-DebugLog ("Calling JoinDomainOrWorkgroup with workgroup {0}" -f $workgroup_name)
try {
$swg_result = Get-CimInstance Win32_ComputerSystem | Invoke-CimMethod -MethodName JoinDomainOrWorkgroup -Arguments @{Name="$workgroup_name"}
} catch {
Fail-Json -obj $result -message "failed to call Win32_ComputerSystem.JoinDomainOrWorkgroup($workgroup_name): $($_.Exception.Message)"
}
if ($swg_result.ReturnValue -ne 0) {
Fail-Json -obj $result -message "failed to set workgroup through WMI, return value: $($swg_result.ReturnValue)"
}
}
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
try {
Remove-Computer -Workgroup $workgroup_name -Credential $domain_cred -Force
} catch {
Fail-Json -obj $result -message "failed to remove computer from domain: $($_.Exception.Message)"
}
}
# we're already on a workgroup- change it.
Else {
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
$domain_ou_path = Get-AnsibleParam $params "domain_ou_path"
$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
}
If($null -ne $domain_ou_path){ # If OU Path is not empty
Write-DebugLog "adding domain_ou_path to domain-join args"
$join_args.domain_ou_path = $domain_ou_path
}
Join-Domain @join_args
# this change requires a reboot
$result.reboot_required = $true
}
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-Computer @rename_args
# this change requires a reboot
$result.reboot_required = $true
} Else {
# no change is needed
}
}
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-Workgroup -workgroup_name $workgroup_name -domain_admin_user $domain_admin_user -domain_admin_password $domain_admin_password
# this change requires a reboot
$result.reboot_required = $true
}
If(-not $hostname_match) {
Write-DebugLog ("setting hostname to {0}" -f $hostname)
Rename-Computer -NewName $hostname
# this change requires a reboot
$result.reboot_required = $true
}
}
}
default { throw "invalid state $state" }
}
Exit-Json $result
}
Catch {
$excep = $_
Write-DebugLog "Exception: $($excep | out-string)"
Throw
}

@ -1,106 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Red Hat, Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
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.
type: str
domain_admin_user:
description:
- Username of a domain admin for the target domain (required to join or leave the domain).
type: str
required: yes
domain_admin_password:
description:
- Password for the specified C(domain_admin_user).
type: str
hostname:
description:
- The desired hostname for the Windows host.
type: str
domain_ou_path:
description:
- The desired OU path for adding the computer object.
- This is only used when adding the target host to a domain, if it is already a member then it is ignored.
type: str
version_added: "2.4"
state:
description:
- Whether the target host should be a member of a domain or workgroup.
type: str
choices: [ domain, workgroup ]
workgroup_name:
description:
- When C(state) is C(workgroup), the name of the workgroup that the Windows host should be in.
type: str
seealso:
- module: win_domain
- module: win_domain_controller
- module: win_domain_computer
- module: win_domain_group
- module: win_domain_user
- module: win_group
- module: win_group_membership
- module: win_user
author:
- Matt Davis (@nitzmahone)
'''
RETURN = r'''
reboot_required:
description: True if changes were made that require a reboot.
returned: always
type: bool
sample: true
'''
EXAMPLES = r'''
# 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!
domain_ou_path: "OU=Windows,OU=Servers,DC=ansible,DC=vagrant"
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
'''

@ -1,398 +0,0 @@
#!powershell
# Copyright: (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Version 5
Function ConvertTo-ArgSpecType {
<#
.SYNOPSIS
Converts the DSC parameter type to the arg spec type required for Ansible.
#>
param(
[Parameter(Mandatory=$true)][String]$CimType
)
$arg_type = switch($CimType) {
Boolean { "bool" }
Char16 { [Func[[Object], [Char]]]{ [System.Char]::Parse($args[0].ToString()) } }
DateTime { [Func[[Object], [DateTime]]]{ [System.DateTime]($args[0].ToString()) } }
Instance { "dict" }
Real32 { "float" }
Real64 { [Func[[Object], [Double]]]{ [System.Double]::Parse($args[0].ToString()) } }
Reference { "dict" }
SInt16 { [Func[[Object], [Int16]]]{ [System.Int16]::Parse($args[0].ToString()) } }
SInt32 { "int" }
SInt64 { [Func[[Object], [Int64]]]{ [System.Int64]::Parse($args[0].ToString()) } }
SInt8 { [Func[[Object], [SByte]]]{ [System.SByte]::Parse($args[0].ToString()) } }
String { "str" }
UInt16 { [Func[[Object], [UInt16]]]{ [System.UInt16]::Parse($args[0].ToString()) } }
UInt32 { [Func[[Object], [UInt32]]]{ [System.UInt32]::Parse($args[0].ToString()) } }
UInt64 { [Func[[Object], [UInt64]]]{ [System.UInt64]::Parse($args[0].ToString()) } }
UInt8 { [Func[[Object], [Byte]]]{ [System.Byte]::Parse($args[0].ToString()) } }
Unknown { "raw" }
default { "raw" }
}
return $arg_type
}
Function Get-DscCimClassProperties {
<#
.SYNOPSIS
Get's a list of CimProperties of a CIM Class. It filters out any magic or
read only properties that we don't need to know about.
#>
param([Parameter(Mandatory=$true)][String]$ClassName)
$resource = Get-CimClass -ClassName $ClassName -Namespace root\Microsoft\Windows\DesiredStateConfiguration
# Filter out any magic properties that are used internally on an OMI_BaseResource
# https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/DscSupport/CimDSCParser.cs#L1203
$magic_properties = @("ResourceId", "SourceInfo", "ModuleName", "ModuleVersion", "ConfigurationName")
$properties = $resource.CimClassProperties | Where-Object {
($resource.CimSuperClassName -ne "OMI_BaseResource" -or $_.Name -notin $magic_properties) -and
-not $_.Flags.HasFlag([Microsoft.Management.Infrastructure.CimFlags]::ReadOnly)
}
return ,$properties
}
Function Add-PropertyOption {
<#
.SYNOPSIS
Adds the spec for the property type to the existing module specification.
#>
param(
[Parameter(Mandatory=$true)][Hashtable]$Spec,
[Parameter(Mandatory=$true)]
[Microsoft.Management.Infrastructure.CimPropertyDeclaration]$Property
)
$option = @{
required = $false
}
$property_name = $Property.Name
$property_type = $Property.CimType.ToString()
if ($Property.Flags.HasFlag([Microsoft.Management.Infrastructure.CimFlags]::Key) -or
$Property.Flags.HasFlag([Microsoft.Management.Infrastructure.CimFlags]::Required)) {
$option.required = $true
}
if ($null -ne $Property.Qualifiers['Values']) {
$option.choices = [System.Collections.Generic.List`1[Object]]$Property.Qualifiers['Values'].Value
}
if ($property_name -eq "Name") {
# For backwards compatibility we support specifying the Name DSC property as item_name
$option.aliases = @("item_name")
} elseif ($property_name -ceq "key") {
# There seems to be a bug in the CIM property parsing when the property name is 'Key'. The CIM instance will
# think the name is 'key' when the MOF actually defines it as 'Key'. We set the proper casing so the module arg
# validator won't fire a case sensitive warning
$property_name = "Key"
}
if ($Property.ReferenceClassName -eq "MSFT_Credential") {
# Special handling for the MSFT_Credential type (PSCredential), we handle this with having 2 options that
# have the suffix _username and _password.
$option_spec_pass = @{
type = "str"
required = $option.required
no_log = $true
}
$Spec.options."$($property_name)_password" = $option_spec_pass
$Spec.required_together.Add(@("$($property_name)_username", "$($property_name)_password")) > $null
$property_name = "$($property_name)_username"
$option.type = "str"
} elseif ($Property.ReferenceClassName -eq "MSFT_KeyValuePair") {
$option.type = "dict"
} elseif ($property_type.EndsWith("Array")) {
$option.type = "list"
$option.elements = ConvertTo-ArgSpecType -CimType $property_type.Substring(0, $property_type.Length - 5)
} else {
$option.type = ConvertTo-ArgSpecType -CimType $property_type
}
if (($option.type -eq "dict" -or ($option.type -eq "list" -and $option.elements -eq "dict")) -and
$Property.ReferenceClassName -ne "MSFT_KeyValuePair") {
# Get the sub spec if the type is a Instance (CimInstance/dict)
$sub_option_spec = Get-OptionSpec -ClassName $Property.ReferenceClassName
$option += $sub_option_spec
}
$Spec.options.$property_name = $option
}
Function Get-OptionSpec {
<#
.SYNOPSIS
Generates the specifiec used in AnsibleModule for a CIM MOF resource name.
.NOTES
This won't be able to retrieve the default values for an option as that is not defined in the MOF for a resource.
Default values are still preserved in the DSC engine if we don't pass in the property at all, we just can't report
on what they are automatically.
#>
param(
[Parameter(Mandatory=$true)][String]$ClassName
)
$spec = @{
options = @{}
required_together = [System.Collections.ArrayList]@()
}
$properties = Get-DscCimClassProperties -ClassName $ClassName
foreach ($property in $properties) {
Add-PropertyOption -Spec $spec -Property $property
}
return $spec
}
Function ConvertTo-CimInstance {
<#
.SYNOPSIS
Converts a dict to a CimInstance of the specified Class. Also provides a
better error message if this fails that contains the option name that failed.
#>
param(
[Parameter(Mandatory=$true)][String]$Name,
[Parameter(Mandatory=$true)][String]$ClassName,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Value,
[Parameter(Mandatory=$true)][Ansible.Basic.AnsibleModule]$Module,
[Switch]$Recurse
)
$properties = @{}
foreach ($value_info in $Value.GetEnumerator()) {
# Need to remove all null values from existing dict so the conversion works
if ($null -eq $value_info.Value) {
continue
}
$properties.($value_info.Key) = $value_info.Value
}
if ($Recurse) {
# We want to validate and convert and values to what's required by DSC
$properties = ConvertTo-DscProperty -ClassName $ClassName -Params $properties -Module $Module
}
try {
return (New-CimInstance -ClassName $ClassName -Property $properties -ClientOnly)
} catch {
# New-CimInstance raises a poor error message, make sure we mention what option it is for
$Module.FailJson("Failed to cast dict value for option '$Name' to a CimInstance: $($_.Exception.Message)", $_)
}
}
Function ConvertTo-DscProperty {
<#
.SYNOPSIS
Converts the input module parameters that have been validated and casted
into the types expected by the DSC engine. This is mostly done to deal with
types like PSCredential and Dictionaries.
#>
param(
[Parameter(Mandatory=$true)][String]$ClassName,
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Params,
[Parameter(Mandatory=$true)][Ansible.Basic.AnsibleModule]$Module
)
$properties = Get-DscCimClassProperties -ClassName $ClassName
$dsc_properties = @{}
foreach ($property in $properties) {
$property_name = $property.Name
$property_type = $property.CimType.ToString()
if ($property.ReferenceClassName -eq "MSFT_Credential") {
$username = $Params."$($property_name)_username"
$password = $Params."$($property_name)_password"
# No user set == No option set in playbook, skip this property
if ($null -eq $username) {
continue
}
$sec_password = ConvertTo-SecureString -String $password -AsPlainText -Force
$value = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $sec_password
} else {
$value = $Params.$property_name
# The actual value wasn't set, skip adding this property
if ($null -eq $value) {
continue
}
if ($property.ReferenceClassName -eq "MSFT_KeyValuePair") {
$key_value_pairs = [System.Collections.Generic.List`1[CimInstance]]@()
foreach ($value_info in $value.GetEnumerator()) {
$kvp = @{Key = $value_info.Key; Value = $value_info.Value.ToString()}
$cim_instance = ConvertTo-CimInstance -Name $property_name -ClassName MSFT_KeyValuePair `
-Value $kvp -Module $Module
$key_value_pairs.Add($cim_instance) > $null
}
$value = $key_value_pairs.ToArray()
} elseif ($null -ne $property.ReferenceClassName) {
# Convert the dict to a CimInstance (or list of CimInstances)
$convert_args = @{
ClassName = $property.ReferenceClassName
Module = $Module
Name = $property_name
Recurse = $true
}
if ($property_type.EndsWith("Array")) {
$value = [System.Collections.Generic.List`1[CimInstance]]@()
foreach ($raw in $Params.$property_name.GetEnumerator()) {
$cim_instance = ConvertTo-CimInstance -Value $raw @convert_args
$value.Add($cim_instance) > $null
}
$value = $value.ToArray() # Need to make sure we are dealing with an Array not a List
} else {
$value = ConvertTo-CimInstance -Value $value @convert_args
}
}
}
$dsc_properties.$property_name = $value
}
return $dsc_properties
}
Function Invoke-DscMethod {
<#
.SYNOPSIS
Invokes the DSC Resource Method specified in another PS pipeline. This is
done so we can retrieve the Verbose stream and return it back to the user
for futher debugging.
#>
param(
[Parameter(Mandatory=$true)][Ansible.Basic.AnsibleModule]$Module,
[Parameter(Mandatory=$true)][String]$Method,
[Parameter(Mandatory=$true)][Hashtable]$Arguments
)
# Invoke the DSC resource in a separate runspace so we can capture the Verbose output
$ps = [PowerShell]::Create()
$ps.AddCommand("Invoke-DscResource").AddParameter("Method", $Method) > $null
$ps.AddParameters($Arguments) > $null
$result = $ps.Invoke()
# Pass the warnings through to the AnsibleModule return result
foreach ($warning in $ps.Streams.Warning) {
$Module.Warn($warning.Message)
}
# If running at a high enough verbosity, add the verbose output to the AnsibleModule return result
if ($Module.Verbosity -ge 3) {
$verbose_logs = [System.Collections.Generic.List`1[String]]@()
foreach ($verbosity in $ps.Streams.Verbose) {
$verbose_logs.Add($verbosity.Message) > $null
}
$Module.Result."verbose_$($Method.ToLower())" = $verbose_logs
}
if ($ps.HadErrors) {
# Cannot pass in the ErrorRecord as it's a RemotingErrorRecord and doesn't contain the ScriptStackTrace
# or other info that would be useful
$Module.FailJson("Failed to invoke DSC $Method method: $($ps.Streams.Error[0].Exception.Message)")
}
return $result
}
# win_dsc is unique in that is builds the arg spec based on DSC Resource input. To get this info
# we need to read the resource_name and module_version value which is done outside of Ansible.Basic
if ($args.Length -gt 0) {
$params = Get-Content -Path $args[0] | ConvertFrom-Json
} else {
$params = $complex_args
}
if (-not $params.ContainsKey("resource_name")) {
$res = @{
msg = "missing required argument: resource_name"
failed = $true
}
Write-Output -InputObject (ConvertTo-Json -Compress -InputObject $res)
exit 1
}
$resource_name = $params.resource_name
if ($params.ContainsKey("module_version")) {
$module_version = $params.module_version
} else {
$module_version = "latest"
}
$module_versions = (Get-DscResource -Name $resource_name -ErrorAction SilentlyContinue | Sort-Object -Property Version)
$resource = $null
if ($module_version -eq "latest" -and $null -ne $module_versions) {
$resource = $module_versions[-1]
} elseif ($module_version -ne "latest") {
$resource = $module_versions | Where-Object { $_.Version -eq $module_version }
}
if (-not $resource) {
if ($module_version -eq "latest") {
$msg = "Resource '$resource_name' not found."
} else {
$msg = "Resource '$resource_name' with version '$module_version' not found."
$msg += " Versions installed: '$($module_versions.Version -join "', '")'."
}
Write-Output -InputObject (ConvertTo-Json -Compress -InputObject @{ failed = $true; msg = $msg })
exit 1
}
# Build the base args for the DSC Invocation based on the resource selected
$dsc_args = @{
Name = $resource.Name
}
# Binary resources are not working very well with that approach - need to guesstimate module name/version
$module_version = $null
if ($resource.Module) {
$dsc_args.ModuleName = @{
ModuleName = $resource.Module.Name
ModuleVersion = $resource.Module.Version
}
$module_version = $resource.Module.Version.ToString()
} else {
$dsc_args.ModuleName = "PSDesiredStateConfiguration"
}
# To ensure the class registered with CIM is the one based on our version, we want to run the Get method so the DSC
# engine updates the metadata propery. We don't care about any errors here
try {
Invoke-DscResource -Method Get -Property @{Fake="Fake"} @dsc_args > $null
} catch {}
# Dynamically build the option spec based on the resource_name specified and create the module object
$spec = Get-OptionSpec -ClassName $resource.ResourceType
$spec.supports_check_mode = $true
$spec.options.module_version = @{ type = "str"; default = "latest" }
$spec.options.resource_name = @{ type = "str"; required = $true }
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$module.Result.reboot_required = $false
$module.Result.module_version = $module_version
# Build the DSC invocation arguments and invoke the resource
$dsc_args.Property = ConvertTo-DscProperty -ClassName $resource.ResourceType -Module $module -Params $Module.Params
$dsc_args.Verbose = $true
$test_result = Invoke-DscMethod -Module $module -Method Test -Arguments $dsc_args
if ($test_result.InDesiredState -ne $true) {
if (-not $module.CheckMode) {
$result = Invoke-DscMethod -Module $module -Method Set -Arguments $dsc_args
$module.Result.reboot_required = $result.RebootRequired
}
$module.Result.changed = $true
}
$module.ExitJson()

@ -1,183 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Trond Hindenes <trond@hindenes.com>, and others
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_dsc
version_added: "2.4"
short_description: Invokes a PowerShell DSC configuration
description:
- Configures a resource using PowerShell DSC.
- Requires PowerShell version 5.0 or newer.
- Most of the options for this module are dynamic and will vary depending on
the DSC Resource specified in I(resource_name).
- See :doc:`/user_guide/windows_dsc` for more information on how to use this module.
options:
resource_name:
description:
- The name of the DSC Resource to use.
- Must be accessible to PowerShell using any of the default paths.
type: str
required: yes
module_version:
description:
- Can be used to configure the exact version of the DSC resource to be
invoked.
- Useful if the target node has multiple versions installed of the module
containing the DSC resource.
- If not specified, the module will follow standard PowerShell convention
and use the highest version available.
type: str
default: latest
free_form:
description:
- The M(win_dsc) module takes in multiple free form options based on the
DSC resource being invoked by I(resource_name).
- There is no option actually named C(free_form) so see the examples.
- This module will try and convert the option to the correct type required
by the DSC resource and throw a warning if it fails.
- If the type of the DSC resource option is a C(CimInstance) or
C(CimInstance[]), this means the value should be a dictionary or list
of dictionaries based on the values required by that option.
- If the type of the DSC resource option is a C(PSCredential) then there
needs to be 2 options set in the Ansible task definition suffixed with
C(_username) and C(_password).
- If the type of the DSC resource option is an array, then a list should be
provided but a comma separated string also work. Use a list where
possible as no escaping is required and it works with more complex types
list C(CimInstance[]).
- If the type of the DSC resource option is a C(DateTime), you should use
a string in the form of an ISO 8901 string to ensure the exact date is
used.
- Since Ansible 2.8, Ansible will now validate the input fields against the
DSC resource definition automatically. Older versions will silently
ignore invalid fields.
type: str
required: true
notes:
- By default there are a few builtin resources that come with PowerShell 5.0,
see U(https://docs.microsoft.com/en-us/powershell/scripting/dsc/resources/resources) for
more information on these resources.
- Custom DSC resources can be installed with M(win_psmodule) using the I(name)
option.
- The DSC engine run's each task as the SYSTEM account, any resources that need
to be accessed with a different account need to have C(PsDscRunAsCredential)
set.
- To see the valid options for a DSC resource, run the module with C(-vvv) to
show the possible module invocation. Default values are not shown in this
output but are applied within the DSC engine.
author:
- Trond Hindenes (@trondhindenes)
'''
EXAMPLES = r'''
- name: Extract zip file
win_dsc:
resource_name: Archive
Ensure: Present
Path: C:\Temp\zipfile.zip
Destination: C:\Temp\Temp2
- name: Install a Windows feature with the WindowsFeature resource
win_dsc:
resource_name: WindowsFeature
Name: telnet-client
- name: Edit HKCU reg key under specific user
win_dsc:
resource_name: Registry
Ensure: Present
Key: HKEY_CURRENT_USER\ExampleKey
ValueName: TestValue
ValueData: TestData
PsDscRunAsCredential_username: '{{ansible_user}}'
PsDscRunAsCredential_password: '{{ansible_password}}'
no_log: true
- name: Create file with multiple attributes
win_dsc:
resource_name: File
DestinationPath: C:\ansible\dsc
Attributes: # can also be a comma separated string, e.g. 'Hidden, System'
- Hidden
- System
Ensure: Present
Type: Directory
- name: Call DSC resource with DateTime option
win_dsc:
resource_name: DateTimeResource
DateTimeOption: '2019-02-22T13:57:31.2311892+00:00'
# more complex example using custom DSC resource and dict values
- name: Setup the xWebAdministration module
win_psmodule:
name: xWebAdministration
state: present
- name: Create IIS Website with Binding and Authentication options
win_dsc:
resource_name: xWebsite
Ensure: Present
Name: DSC Website
State: Started
PhysicalPath: C:\inetpub\wwwroot
BindingInfo: # Example of a CimInstance[] DSC parameter (list of dicts)
- Protocol: https
Port: 1234
CertificateStoreName: MY
CertificateThumbprint: C676A89018C4D5902353545343634F35E6B3A659
HostName: DSCTest
IPAddress: '*'
SSLFlags: '1'
- Protocol: http
Port: 4321
IPAddress: '*'
AuthenticationInfo: # Example of a CimInstance DSC parameter (dict)
Anonymous: no
Basic: true
Digest: false
Windows: yes
'''
RETURN = r'''
module_version:
description: The version of the dsc resource/module used.
returned: always
type: str
sample: "1.0.1"
reboot_required:
description: Flag returned from the DSC engine indicating whether or not
the machine requires a reboot for the invoked changes to take effect.
returned: always
type: bool
sample: true
verbose_test:
description: The verbose output as a list from executing the DSC test
method.
returned: Ansible verbosity is -vvv or greater
type: list
sample: [
"Perform operation 'Invoke CimMethod' with the following parameters, ",
"[SERVER]: LCM: [Start Test ] [[File]DirectResourceAccess]",
"Operation 'Invoke CimMethod' complete."
]
verbose_set:
description: The verbose output as a list from executing the DSC Set
method.
returned: Ansible verbosity is -vvv or greater and a change occurred
type: list
sample: [
"Perform operation 'Invoke CimMethod' with the following parameters, ",
"[SERVER]: LCM: [Start Set ] [[File]DirectResourceAccess]",
"Operation 'Invoke CimMethod' complete."
]
'''

@ -1,61 +0,0 @@
#!powershell
# Copyright: (c) 2015, Jon Hawkesworth (@jhawkesworth) <figs@unity.demon.co.uk>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
$spec = @{
options = @{
name = @{ type = "str"; required = $true }
level = @{ type = "str"; choices = "machine", "process", "user"; required = $true }
state = @{ type = "str"; choices = "absent", "present"; default = "present" }
value = @{ type = "str" }
}
required_if = @(,@("state", "present", @("value")))
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$name = $module.Params.name
$level = $module.Params.level
$state = $module.Params.state
$value = $module.Params.value
$before_value = [Environment]::GetEnvironmentVariable($name, $level)
$module.Result.before_value = $before_value
$module.Result.value = $value
# When removing environment, set value to $null if set
if ($state -eq "absent" -and $value) {
$module.Warn("When removing environment variable '$name' it should not have a value '$value' set")
$value = $null
} elseif ($state -eq "present" -and (-not $value)) {
$module.FailJson("When state=present, value must be defined and not an empty string, if you wish to remove the envvar, set state=absent")
}
$module.Diff.before = @{ $level = @{} }
if ($before_value) {
$module.Diff.before.$level.$name = $before_value
}
$module.Diff.after = @{ $level = @{} }
if ($value) {
$module.Diff.after.$level.$name = $value
}
if ($state -eq "present" -and $before_value -ne $value) {
if (-not $module.CheckMode) {
[Environment]::SetEnvironmentVariable($name, $value, $level)
}
$module.Result.changed = $true
} elseif ($state -eq "absent" -and $null -ne $before_value) {
if (-not $module.CheckMode) {
[Environment]::SetEnvironmentVariable($name, $null, $level)
}
$module.Result.changed = $true
}
$module.ExitJson()

@ -1,89 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Jon Hawkesworth (@jhawkesworth) <figs@unity.demon.co.uk>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_environment
version_added: '2.0'
short_description: Modify environment variables on windows hosts
description:
- Uses .net Environment to set or remove environment variables and can set at User, Machine or Process level.
- User level environment variables will be set, but not available until the user has logged off and on again.
options:
state:
description:
- Set to C(present) to ensure environment variable is set.
- Set to C(absent) to ensure it is removed.
type: str
choices: [ absent, present ]
default: present
name:
description:
- The name of the environment variable.
type: str
required: yes
value:
description:
- The value to store in the environment variable.
- Must be set when C(state=present) and cannot be an empty string.
- Can be omitted for C(state=absent).
type: str
level:
description:
- The level at which to set the environment variable.
- Use C(machine) to set for all users.
- Use C(user) to set for the current user that ansible is connected as.
- Use C(process) to set for the current process. Probably not that useful.
type: str
required: yes
choices: [ machine, process, user ]
notes:
- This module is best-suited for setting the entire value of an
environment variable. For safe element-based management of
path-like environment vars, use the M(win_path) module.
- This module does not broadcast change events.
This means that the minority of windows applications which can have
their environment changed without restarting will not be notified and
therefore will need restarting to pick up new environment settings.
User level environment variables will require the user to log out
and in again before they become available.
seealso:
- module: win_path
author:
- Jon Hawkesworth (@jhawkesworth)
'''
EXAMPLES = r'''
- name: Set an environment variable for all users
win_environment:
state: present
name: TestVariable
value: Test value
level: machine
- name: Remove an environment variable for the current user
win_environment:
state: absent
name: TestVariable
level: user
'''
RETURN = r'''
before_value:
description: the value of the environment key before a change, this is null if it didn't exist
returned: always
type: str
sample: C:\Windows\System32
value:
description: the value the environment key has been set to, this is null if removed
returned: always
type: str
sample: C:\Program Files\jdk1.8
'''

@ -1,111 +0,0 @@
#!powershell
# Copyright: (c) 2014, Paul Durivage <paul.durivage@rackspace.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
Import-Module -Name ServerManager
$result = @{
changed = $false
}
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$name = Get-AnsibleParam -obj $params -name "name" -type "list" -failifempty $true
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent"
$include_sub_features = Get-AnsibleParam -obj $params -name "include_sub_features" -type "bool" -default $false
$include_management_tools = Get-AnsibleParam -obj $params -name "include_management_tools" -type "bool" -default $false
$source = Get-AnsibleParam -obj $params -name "source" -type "str"
$install_cmdlet = $false
if (Get-Command -Name Install-WindowsFeature -ErrorAction SilentlyContinue) {
Set-Alias -Name Install-AnsibleWindowsFeature -Value Install-WindowsFeature
Set-Alias -Name Uninstall-AnsibleWindowsFeature -Value Uninstall-WindowsFeature
$install_cmdlet = $true
} elseif (Get-Command -Name Add-WindowsFeature -ErrorAction SilentlyContinue) {
Set-Alias -Name Install-AnsibleWindowsFeature -Value Add-WindowsFeature
Set-Alias -Name Uninstall-AnsibleWindowsFeature -Value Remove-WindowsFeature
} else {
Fail-Json -obj $result -message "This version of Windows does not support the cmdlets Install-WindowsFeature or Add-WindowsFeature"
}
if ($state -eq "present") {
$install_args = @{
Name = $name
IncludeAllSubFeature = $include_sub_features
Restart = $false
WhatIf = $check_mode
ErrorAction = "Stop"
}
if ($install_cmdlet) {
$install_args.IncludeManagementTools = $include_management_tools
$install_args.Confirm = $false
if ($source) {
if (-not (Test-Path -Path $source)) {
Fail-Json -obj $result -message "Failed to find source path $source for feature install"
}
$install_args.Source = $source
}
}
try {
$action_results = Install-AnsibleWindowsFeature @install_args
} catch {
Fail-Json -obj $result -message "Failed to install Windows Feature: $($_.Exception.Message)"
}
} else {
$uninstall_args = @{
Name = $name
Restart = $false
WhatIf = $check_mode
ErrorAction = "Stop"
}
if ($install_cmdlet) {
$uninstall_args.IncludeManagementTools = $include_management_tools
}
try {
$action_results = Uninstall-AnsibleWindowsFeature @uninstall_args
} catch {
Fail-Json -obj $result -message "Failed to uninstall Windows Feature: $($_.Exception.Message)"
}
}
# Loop through results and create a hash containing details about
# each role/feature that is installed/removed
# $action_results.FeatureResult is not empty if anything was changed
$feature_results = @()
foreach ($action_result in $action_results.FeatureResult) {
$message = @()
foreach ($msg in $action_result.Message) {
$message += @{
message_type = $msg.MessageType.ToString()
error_code = $msg.ErrorCode
text = $msg.Text
}
}
$feature_results += @{
id = $action_result.Id
display_name = $action_result.DisplayName
message = $message
reboot_required = ConvertTo-Bool -obj $action_result.RestartNeeded
skip_reason = $action_result.SkipReason.ToString()
success = ConvertTo-Bool -obj $action_result.Success
restart_needed = ConvertTo-Bool -obj $action_result.RestartNeeded
}
$result.changed = $true
}
$result.feature_result = $feature_results
$result.success = ConvertTo-Bool -obj $action_results.Success
$result.exitcode = $action_results.ExitCode.ToString()
$result.reboot_required = ConvertTo-Bool -obj $action_results.RestartNeeded
# controls whether Ansible will fail or not
$result.failed = (-not $action_results.Success)
Exit-Json -obj $result

@ -1,149 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2014, Paul Durivage <paul.durivage@rackspace.com>
# Copyright: (c) 2014, Trond Hindenes <trond@hindenes.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_feature
version_added: "1.7"
short_description: Installs and uninstalls Windows Features on Windows Server
description:
- Installs or uninstalls Windows Roles or Features on Windows Server.
- This module uses the Add/Remove-WindowsFeature Cmdlets on Windows 2008 R2
and Install/Uninstall-WindowsFeature Cmdlets on Windows 2012, which are not available on client os machines.
options:
name:
description:
- Names of roles or features to install as a single feature or a comma-separated list of features.
- To list all available features use the PowerShell command C(Get-WindowsFeature).
type: list
required: yes
state:
description:
- State of the features or roles on the system.
type: str
choices: [ absent, present ]
default: present
include_sub_features:
description:
- Adds all subfeatures of the specified feature.
type: bool
default: no
include_management_tools:
description:
- Adds the corresponding management tools to the specified feature.
- Not supported in Windows 2008 R2 and will be ignored.
type: bool
default: no
source:
description:
- Specify a source to install the feature from.
- Not supported in Windows 2008 R2 and will be ignored.
- Can either be C({driveletter}:\sources\sxs) or C(\\{IP}\share\sources\sxs).
type: str
version_added: "2.1"
seealso:
- module: win_chocolatey
- module: win_package
author:
- Paul Durivage (@angstwad)
- Trond Hindenes (@trondhindenes)
'''
EXAMPLES = r'''
- name: Install IIS (Web-Server only)
win_feature:
name: Web-Server
state: present
- name: Install IIS (Web-Server and Web-Common-Http)
win_feature:
name:
- Web-Server
- Web-Common-Http
state: present
- name: Install NET-Framework-Core from file
win_feature:
name: NET-Framework-Core
source: C:\Temp\iso\sources\sxs
state: present
- name: Install IIS Web-Server with sub features and management tools
win_feature:
name: Web-Server
state: present
include_sub_features: yes
include_management_tools: yes
register: win_feature
- name: Reboot if installing Web-Server feature requires it
win_reboot:
when: win_feature.reboot_required
'''
RETURN = r'''
exitcode:
description: The stringified exit code from the feature installation/removal command.
returned: always
type: str
sample: Success
feature_result:
description: List of features that were installed or removed.
returned: success
type: complex
sample:
contains:
display_name:
description: Feature display name.
returned: always
type: str
sample: "Telnet Client"
id:
description: A list of KB article IDs that apply to the update.
returned: always
type: int
sample: 44
message:
description: Any messages returned from the feature subsystem that occurred during installation or removal of this feature.
returned: always
type: list
elements: str
sample: []
reboot_required:
description: True when the target server requires a reboot as a result of installing or removing this feature.
returned: always
type: bool
sample: true
restart_needed:
description: DEPRECATED in Ansible 2.4 (refer to C(reboot_required) instead). True when the target server requires a reboot as a
result of installing or removing this feature.
returned: always
type: bool
sample: true
skip_reason:
description: The reason a feature installation or removal was skipped.
returned: always
type: str
sample: NotSkipped
success:
description: If the feature installation or removal was successful.
returned: always
type: bool
sample: true
reboot_required:
description: True when the target server requires a reboot to complete updates (no further updates can be installed until after a reboot).
returned: success
type: bool
sample: true
'''

@ -1,152 +0,0 @@
#!powershell
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
$ErrorActionPreference = "Stop"
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false
$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP
$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true -aliases "dest","name"
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -validateset "absent","directory","file","touch"
# used in template/copy when dest is the path to a dir and source is a file
$original_basename = Get-AnsibleParam -obj $params -name "_original_basename" -type "str"
if ((Test-Path -LiteralPath $path -PathType Container) -and ($null -ne $original_basename)) {
$path = Join-Path -Path $path -ChildPath $original_basename
}
$result = @{
changed = $false
}
# Used to delete symlinks as powershell cannot delete broken symlinks
$symlink_util = @"
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace Ansible.Command {
public class SymLinkHelper {
[DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern bool DeleteFileW(string lpFileName);
[DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern bool RemoveDirectoryW(string lpPathName);
public static void DeleteDirectory(string path) {
if (!RemoveDirectoryW(path))
throw new Exception(String.Format("RemoveDirectoryW({0}) failed: {1}", path, new Win32Exception(Marshal.GetLastWin32Error()).Message));
}
public static void DeleteFile(string path) {
if (!DeleteFileW(path))
throw new Exception(String.Format("DeleteFileW({0}) failed: {1}", path, new Win32Exception(Marshal.GetLastWin32Error()).Message));
}
}
}
"@
$original_tmp = $env:TMP
$env:TMP = $_remote_tmp
Add-Type -TypeDefinition $symlink_util
$env:TMP = $original_tmp
# Used to delete directories and files with logic on handling symbolic links
function Remove-File($file, $checkmode) {
try {
if ($file.Attributes -band [System.IO.FileAttributes]::ReparsePoint) {
# Bug with powershell, if you try and delete a symbolic link that is pointing
# to an invalid path it will fail, using Win32 API to do this instead
if ($file.PSIsContainer) {
if (-not $checkmode) {
[Ansible.Command.SymLinkHelper]::DeleteDirectory($file.FullName)
}
} else {
if (-not $checkmode) {
[Ansible.Command.SymlinkHelper]::DeleteFile($file.FullName)
}
}
} elseif ($file.PSIsContainer) {
Remove-Directory -directory $file -checkmode $checkmode
} else {
Remove-Item -LiteralPath $file.FullName -Force -WhatIf:$checkmode
}
} catch [Exception] {
Fail-Json $result "Failed to delete $($file.FullName): $($_.Exception.Message)"
}
}
function Remove-Directory($directory, $checkmode) {
foreach ($file in Get-ChildItem -LiteralPath $directory.FullName) {
Remove-File -file $file -checkmode $checkmode
}
Remove-Item -LiteralPath $directory.FullName -Force -Recurse -WhatIf:$checkmode
}
if ($state -eq "touch") {
if (Test-Path -LiteralPath $path) {
if (-not $check_mode) {
(Get-ChildItem -LiteralPath $path).LastWriteTime = Get-Date
}
$result.changed = $true
} else {
Write-Output $null | Out-File -LiteralPath $path -Encoding ASCII -WhatIf:$check_mode
$result.changed = $true
}
}
if (Test-Path -LiteralPath $path) {
$fileinfo = Get-Item -LiteralPath $path -Force
if ($state -eq "absent") {
Remove-File -file $fileinfo -checkmode $check_mode
$result.changed = $true
} else {
if ($state -eq "directory" -and -not $fileinfo.PsIsContainer) {
Fail-Json $result "path $path is not a directory"
}
if ($state -eq "file" -and $fileinfo.PsIsContainer) {
Fail-Json $result "path $path is not a file"
}
}
} else {
# If state is not supplied, test the $path to see if it looks like
# a file or a folder and set state to file or folder
if ($null -eq $state) {
$basename = Split-Path -Path $path -Leaf
if ($basename.length -gt 0) {
$state = "file"
} else {
$state = "directory"
}
}
if ($state -eq "directory") {
try {
New-Item -Path $path -ItemType Directory -WhatIf:$check_mode | Out-Null
} catch {
if ($_.CategoryInfo.Category -eq "ResourceExists") {
$fileinfo = Get-Item -LiteralPath $_.CategoryInfo.TargetName
if ($state -eq "directory" -and -not $fileinfo.PsIsContainer) {
Fail-Json $result "path $path is not a directory"
}
} else {
Fail-Json $result $_.Exception.Message
}
}
$result.changed = $true
} elseif ($state -eq "file") {
Fail-Json $result "path $path will not be created"
}
}
Exit-Json $result

@ -1,70 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Jon Hawkesworth (@jhawkesworth) <figs@unity.demon.co.uk>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_file
version_added: "1.9.2"
short_description: Creates, touches or removes files or directories
description:
- Creates (empty) files, updates file modification stamps of existing files,
and can create or remove directories.
- Unlike M(file), does not modify ownership, permissions or manipulate links.
- For non-Windows targets, use the M(file) module instead.
options:
path:
description:
- Path to the file being managed.
required: yes
type: path
aliases: [ dest, name ]
state:
description:
- If C(directory), all immediate subdirectories will be created if they
do not exist.
- If C(file), the file will NOT be created if it does not exist, see the M(copy)
or M(template) module if you want that behavior.
- If C(absent), directories will be recursively deleted, and files will be removed.
- If C(touch), an empty file will be created if the C(path) does not
exist, while an existing file or directory will receive updated file access and
modification times (similar to the way C(touch) works from the command line).
type: str
choices: [ absent, directory, file, touch ]
seealso:
- module: file
- module: win_acl
- module: win_acl_inheritance
- module: win_owner
- module: win_stat
author:
- Jon Hawkesworth (@jhawkesworth)
'''
EXAMPLES = r'''
- name: Touch a file (creates if not present, updates modification time if present)
win_file:
path: C:\Temp\foo.conf
state: touch
- name: Remove a file, if present
win_file:
path: C:\Temp\foo.conf
state: absent
- name: Create directory structure
win_file:
path: C:\Temp\folder\subfolder
state: directory
- name: Remove directory structure
win_file:
path: C:\Temp
state: absent
'''

@ -1,416 +0,0 @@
#!powershell
# Copyright: (c) 2016, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.LinkUtil
$spec = @{
options = @{
paths = @{ type = "list"; elements = "str"; required = $true }
age = @{ type = "str" }
age_stamp = @{ type = "str"; default = "mtime"; choices = "mtime", "ctime", "atime" }
file_type = @{ type = "str"; default = "file"; choices = "file", "directory" }
follow = @{ type = "bool"; default = $false }
hidden = @{ type = "bool"; default = $false }
patterns = @{ type = "list"; elements = "str"; aliases = "regex", "regexp" }
recurse = @{ type = "bool"; default = $false }
size = @{ type = "str" }
use_regex = @{ type = "bool"; default = $false }
get_checksum = @{ type = "bool"; default = $true }
checksum_algorithm = @{ type = "str"; default = "sha1"; choices = "md5", "sha1", "sha256", "sha384", "sha512" }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$paths = $module.Params.paths
$age = $module.Params.age
$age_stamp = $module.Params.age_stamp
$file_type = $module.Params.file_type
$follow = $module.Params.follow
$hidden = $module.Params.hidden
$patterns = $module.Params.patterns
$recurse = $module.Params.recurse
$size = $module.Params.size
$use_regex = $module.Params.use_regex
$get_checksum = $module.Params.get_checksum
$checksum_algorithm = $module.Params.checksum_algorithm
$module.Result.examined = 0
$module.Result.files = @()
$module.Result.matched = 0
Load-LinkUtils
Function Assert-Age {
Param (
[System.IO.FileSystemInfo]$File,
[System.Int64]$Age,
[System.String]$AgeStamp
)
$actual_age = switch ($AgeStamp) {
mtime { $File.LastWriteTime.Ticks }
ctime { $File.CreationTime.Ticks }
atime { $File.LastAccessTime.Ticks }
}
if ($Age -ge 0) {
return $Age -ge $actual_age
} else {
return ($Age * -1) -le $actual_age
}
}
Function Assert-FileType {
Param (
[System.IO.FileSystemInfo]$File,
[System.String]$FileType
)
$is_dir = $File.Attributes.HasFlag([System.IO.FileAttributes]::Directory)
return ($FileType -eq 'directory' -and $is_dir) -or ($FileType -eq 'file' -and -not $is_dir)
}
Function Assert-FileHidden {
Param (
[System.IO.FileSystemInfo]$File,
[Switch]$IsHidden
)
$file_is_hidden = $File.Attributes.HasFlag([System.IO.FileAttributes]::Hidden)
return $IsHidden.IsPresent -eq $file_is_hidden
}
Function Assert-FileNamePattern {
Param (
[System.IO.FileSystemInfo]$File,
[System.String[]]$Patterns,
[Switch]$UseRegex
)
$valid_match = $false
foreach ($pattern in $Patterns) {
if ($UseRegex) {
if ($File.Name -match $pattern) {
$valid_match = $true
break
}
} else {
if ($File.Name -like $pattern) {
$valid_match = $true
break
}
}
}
return $valid_match
}
Function Assert-FileSize {
Param (
[System.IO.FileSystemInfo]$File,
[System.Int64]$Size
)
if ($Size -ge 0) {
return $File.Length -ge $Size
} else {
return $File.Length -le ($Size * -1)
}
}
Function Get-FileChecksum {
Param (
[System.String]$Path,
[System.String]$Algorithm
)
$sp = switch ($algorithm) {
'md5' { New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider }
'sha1' { New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider }
'sha256' { New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider }
'sha384' { New-Object -TypeName System.Security.Cryptography.SHA384CryptoServiceProvider }
'sha512' { New-Object -TypeName System.Security.Cryptography.SHA512CryptoServiceProvider }
}
$fp = [System.IO.File]::Open($Path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
try {
$hash = [System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower()
} finally {
$fp.Dispose()
}
return $hash
}
Function Search-Path {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true)]
[System.String]
$Path,
[Parameter(Mandatory=$true)]
[AllowEmptyCollection()]
[System.Collections.Generic.HashSet`1[System.String]]
$CheckedPaths,
[Parameter(Mandatory=$true)]
[Object]
$Module,
[System.Int64]
$Age,
[System.String]
$AgeStamp,
[System.String]
$FileType,
[Switch]
$Follow,
[Switch]
$GetChecksum,
[Switch]
$IsHidden,
[System.String[]]
$Patterns,
[Switch]
$Recurse,
[System.Int64]
$Size,
[Switch]
$UseRegex
)
$dir_obj = New-Object -TypeName System.IO.DirectoryInfo -ArgumentList $Path
if ([Int32]$dir_obj.Attributes -eq -1) {
$Module.Warn("Argument path '$Path' does not exist, skipping")
return
} elseif (-not $dir_obj.Attributes.HasFlag([System.IO.FileAttributes]::Directory)) {
$Module.Warn("Argument path '$Path' is a file not a directory, skipping")
return
}
$dir_files = @()
try {
$dir_files = $dir_obj.EnumerateFileSystemInfos("*", [System.IO.SearchOption]::TopDirectoryOnly)
} catch [System.IO.DirectoryNotFoundException] { # Broken ReparsePoint/Symlink, cannot enumerate
} catch [System.UnauthorizedAccessException] {} # No ListDirectory permissions, Get-ChildItem ignored this
foreach ($dir_child in $dir_files) {
if ($dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Directory) -and $Recurse) {
if ($Follow -or -not $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::ReparsePoint)) {
$PSBoundParameters.Remove('Path') > $null
Search-Path -Path $dir_child.FullName @PSBoundParameters
}
}
# Check to see if we've already encountered this path and skip if we have.
if (-not $CheckedPaths.Add($dir_child.FullName.ToLowerInvariant())) {
continue
}
$Module.Result.examined++
if ($PSBoundParameters.ContainsKey('Age')) {
$age_match = Assert-Age -File $dir_child -Age $Age -AgeStamp $AgeStamp
} else {
$age_match = $true
}
$file_type_match = Assert-FileType -File $dir_child -FileType $FileType
$hidden_match = Assert-FileHidden -File $dir_child -IsHidden:$IsHidden
if ($PSBoundParameters.ContainsKey('Patterns')) {
$pattern_match = Assert-FileNamePattern -File $dir_child -Patterns $Patterns -UseRegex:$UseRegex.IsPresent
} else {
$pattern_match = $true
}
if ($PSBoundParameters.ContainsKey('Size')) {
$size_match = Assert-FileSize -File $dir_child -Size $Size
} else {
$size_match = $true
}
if (-not ($age_match -and $file_type_match -and $hidden_match -and $pattern_match -and $size_match)) {
continue
}
# It passed all our filters so add it
$module.Result.matched++
# TODO: Make this generic so it can be shared with win_find and win_stat.
$epoch = New-Object -Type System.DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
$file_info = @{
attributes = $dir_child.Attributes.ToString()
checksum = $null
creationtime = (New-TimeSpan -Start $epoch -End $dir_child.CreationTime).TotalSeconds
exists = $true
extension = $null
filename = $dir_child.Name
isarchive = $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Archive)
isdir = $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Directory)
ishidden = $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Hidden)
isreadonly = $dir_child.Attributes.HasFlag([System.IO.FileAttributes]::ReadOnly)
isreg = $false
isshared = $false
lastaccesstime = (New-TimeSpan -Start $epoch -End $dir_child.LastAccessTime).TotalSeconds
lastwritetime = (New-TimeSpan -Start $epoch -End $dir_child.LastWriteTime).TotalSeconds
owner = $null
path = $dir_child.FullName
sharename = $null
size = $null
}
try {
$file_info.owner = $dir_child.GetAccessControl().Owner
} catch {} # May not have rights to get the Owner, historical behaviour is to ignore.
if ($dir_child.Attributes.HasFlag([System.IO.FileAttributes]::Directory)) {
$share_info = Get-CimInstance -ClassName Win32_Share -Filter "Path='$($dir_child.FullName -replace '\\', '\\')'"
if ($null -ne $share_info) {
$file_info.isshared = $true
$file_info.sharename = $share_info.Name
}
} else {
$file_info.extension = $dir_child.Extension
$file_info.isreg = $true
$file_info.size = $dir_child.Length
if ($GetChecksum) {
try {
$file_info.checksum = Get-FileChecksum -Path $dir_child.FullName -Algorithm $checksum_algorithm
} catch {} # Just keep the checksum as $null in the case of a failure.
}
}
# Append the link information if the path is a link
$link_info = @{
isjunction = $false
islnk = $false
nlink = 1
lnk_source = $null
lnk_target = $null
hlnk_targets = @()
}
$link_stat = Get-Link -link_path $dir_child.FullName
if ($null -ne $link_stat) {
switch ($link_stat.Type) {
"SymbolicLink" {
$link_info.islnk = $true
$link_info.isreg = $false
$link_info.lnk_source = $link_stat.AbsolutePath
$link_info.lnk_target = $link_stat.TargetPath
break
}
"JunctionPoint" {
$link_info.isjunction = $true
$link_info.isreg = $false
$link_info.lnk_source = $link_stat.AbsolutePath
$link_info.lnk_target = $link_stat.TargetPath
break
}
"HardLink" {
$link_info.nlink = $link_stat.HardTargets.Count
# remove current path from the targets
$hlnk_targets = $link_info.HardTargets | Where-Object { $_ -ne $dir_child.FullName }
$link_info.hlnk_targets = @($hlnk_targets)
break
}
}
}
foreach ($kv in $link_info.GetEnumerator()) {
$file_info.$($kv.Key) = $kv.Value
}
# Output the file_info object
$file_info
}
}
$search_params = @{
CheckedPaths = [System.Collections.Generic.HashSet`1[System.String]]@()
GetChecksum = $get_checksum
Module = $module
FileType = $file_type
Follow = $follow
IsHidden = $hidden
Recurse = $recurse
}
if ($null -ne $age) {
$seconds_per_unit = @{'s'=1; 'm'=60; 'h'=3600; 'd'=86400; 'w'=604800}
$seconds_pattern = '^(-?\d+)(s|m|h|d|w)?$'
$match = $age -match $seconds_pattern
if ($Match) {
$specified_seconds = [Int64]$Matches[1]
if ($null -eq $Matches[2]) {
$chosen_unit = 's'
} else {
$chosen_unit = $Matches[2]
}
$total_seconds = $specified_seconds * ($seconds_per_unit.$chosen_unit)
if ($total_seconds -ge 0) {
$search_params.Age = (Get-Date).AddSeconds($total_seconds * -1).Ticks
} else {
# Make sure we add the positive value of seconds to current time then make it negative for later comparisons.
$age = (Get-Date).AddSeconds($total_seconds).Ticks
$search_params.Age = $age * -1
}
$search_params.AgeStamp = $age_stamp
} else {
$module.FailJson("Invalid age pattern specified")
}
}
if ($null -ne $patterns) {
$search_params.Patterns = $patterns
$search_params.UseRegex = $use_regex
}
if ($null -ne $size) {
$bytes_per_unit = @{'b'=1; 'k'=1KB; 'm'=1MB; 'g'=1GB;'t'=1TB}
$size_pattern = '^(-?\d+)(b|k|m|g|t)?$'
$match = $size -match $size_pattern
if ($Match) {
$specified_size = [Int64]$Matches[1]
if ($null -eq $Matches[2]) {
$chosen_byte = 'b'
} else {
$chosen_byte = $Matches[2]
}
$search_params.Size = $specified_size * ($bytes_per_unit.$chosen_byte)
} else {
$module.FailJson("Invalid size pattern specified")
}
}
$matched_files = foreach ($path in $paths) {
# Ensure we pass in an absolute path. We use the ExecutionContext as this is based on the PSProvider path not the
# process location which can be different.
$abs_path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($path)
Search-Path -Path $abs_path @search_params
}
# Make sure we sort the files in alphabetical order.
$module.Result.files = @() + ($matched_files | Sort-Object -Property {$_.path})
$module.ExitJson()

@ -1,345 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2016, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_find
version_added: "2.3"
short_description: Return a list of files based on specific criteria
description:
- Return a list of files based on specified criteria.
- Multiple criteria are AND'd together.
- For non-Windows targets, use the M(find) module instead.
options:
age:
description:
- Select files or folders whose age is equal to or greater than
the specified time.
- Use a negative age to find files equal to or less than
the specified time.
- You can choose seconds, minutes, hours, days or weeks
by specifying the first letter of an of
those words (e.g., "2s", "10d", 1w").
type: str
age_stamp:
description:
- Choose the file property against which we compare C(age).
- The default attribute we compare with is the last modification time.
type: str
choices: [ atime, ctime, mtime ]
default: mtime
checksum_algorithm:
description:
- Algorithm to determine the checksum of a file.
- Will throw an error if the host is unable to use specified algorithm.
type: str
choices: [ md5, sha1, sha256, sha384, sha512 ]
default: sha1
file_type:
description: Type of file to search for.
type: str
choices: [ directory, file ]
default: file
follow:
description:
- Set this to C(yes) to follow symlinks in the path.
- This needs to be used in conjunction with C(recurse).
type: bool
default: no
get_checksum:
description:
- Whether to return a checksum of the file in the return info (default sha1),
use C(checksum_algorithm) to change from the default.
type: bool
default: yes
hidden:
description: Set this to include hidden files or folders.
type: bool
default: no
paths:
description:
- List of paths of directories to search for files or folders in.
- This can be supplied as a single path or a list of paths.
type: list
required: yes
patterns:
description:
- One or more (powershell or regex) patterns to compare filenames with.
- The type of pattern matching is controlled by C(use_regex) option.
- The patterns restrict the list of files or folders to be returned based on the filenames.
- For a file to be matched it only has to match with one pattern in a list provided.
type: list
aliases: [ "regex", "regexp" ]
recurse:
description:
- Will recursively descend into the directory looking for files or folders.
type: bool
default: no
size:
description:
- Select files or folders whose size is equal to or greater than the specified size.
- Use a negative value to find files equal to or less than the specified size.
- You can specify the size with a suffix of the byte type i.e. kilo = k, mega = m...
- Size is not evaluated for symbolic links.
type: str
use_regex:
description:
- Will set patterns to run as a regex check if set to C(yes).
type: bool
default: no
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Find files in path
win_find:
paths: D:\Temp
- name: Find hidden files in path
win_find:
paths: D:\Temp
hidden: yes
- name: Find files in multiple paths
win_find:
paths:
- C:\Temp
- D:\Temp
- name: Find files in directory while searching recursively
win_find:
paths: D:\Temp
recurse: yes
- name: Find files in directory while following symlinks
win_find:
paths: D:\Temp
recurse: yes
follow: yes
- name: Find files with .log and .out extension using powershell wildcards
win_find:
paths: D:\Temp
patterns: [ '*.log', '*.out' ]
- name: Find files in path based on regex pattern
win_find:
paths: D:\Temp
patterns: out_\d{8}-\d{6}.log
- name: Find files older than 1 day
win_find:
paths: D:\Temp
age: 86400
- name: Find files older than 1 day based on create time
win_find:
paths: D:\Temp
age: 86400
age_stamp: ctime
- name: Find files older than 1 day with unit syntax
win_find:
paths: D:\Temp
age: 1d
- name: Find files newer than 1 hour
win_find:
paths: D:\Temp
age: -3600
- name: Find files newer than 1 hour with unit syntax
win_find:
paths: D:\Temp
age: -1h
- name: Find files larger than 1MB
win_find:
paths: D:\Temp
size: 1048576
- name: Find files larger than 1GB with unit syntax
win_find:
paths: D:\Temp
size: 1g
- name: Find files smaller than 1MB
win_find:
paths: D:\Temp
size: -1048576
- name: Find files smaller than 1GB with unit syntax
win_find:
paths: D:\Temp
size: -1g
- name: Find folders/symlinks in multiple paths
win_find:
paths:
- C:\Temp
- D:\Temp
file_type: directory
- name: Find files and return SHA256 checksum of files found
win_find:
paths: C:\Temp
get_checksum: yes
checksum_algorithm: sha256
- name: Find files and do not return the checksum
win_find:
paths: C:\Temp
get_checksum: no
'''
RETURN = r'''
examined:
description: The number of files/folders that was checked.
returned: always
type: int
sample: 10
matched:
description: The number of files/folders that match the criteria.
returned: always
type: int
sample: 2
files:
description: Information on the files/folders that match the criteria returned as a list of dictionary elements
for each file matched. The entries are sorted by the path value alphabetically.
returned: success
type: complex
contains:
attributes:
description: attributes of the file at path in raw form.
returned: success, path exists
type: str
sample: "Archive, Hidden"
checksum:
description: The checksum of a file based on checksum_algorithm specified.
returned: success, path exists, path is a file, get_checksum == True
type: str
sample: 09cb79e8fc7453c84a07f644e441fd81623b7f98
creationtime:
description: The create time of the file represented in seconds since epoch.
returned: success, path exists
type: float
sample: 1477984205.15
exists:
description: Whether the file exists, will always be true for M(win_find).
returned: success, path exists
type: bool
sample: true
extension:
description: The extension of the file at path.
returned: success, path exists, path is a file
type: str
sample: ".ps1"
filename:
description: The name of the file.
returned: success, path exists
type: str
sample: temp
hlnk_targets:
description: List of other files pointing to the same file (hard links), excludes the current file.
returned: success, path exists
type: list
sample:
- C:\temp\file.txt
- C:\Windows\update.log
isarchive:
description: If the path is ready for archiving or not.
returned: success, path exists
type: bool
sample: true
isdir:
description: If the path is a directory or not.
returned: success, path exists
type: bool
sample: true
ishidden:
description: If the path is hidden or not.
returned: success, path exists
type: bool
sample: true
isjunction:
description: If the path is a junction point.
returned: success, path exists
type: bool
sample: true
islnk:
description: If the path is a symbolic link.
returned: success, path exists
type: bool
sample: true
isreadonly:
description: If the path is read only or not.
returned: success, path exists
type: bool
sample: true
isreg:
description: If the path is a regular file or not.
returned: success, path exists
type: bool
sample: true
isshared:
description: If the path is shared or not.
returned: success, path exists
type: bool
sample: true
lastaccesstime:
description: The last access time of the file represented in seconds since epoch.
returned: success, path exists
type: float
sample: 1477984205.15
lastwritetime:
description: The last modification time of the file represented in seconds since epoch.
returned: success, path exists
type: float
sample: 1477984205.15
lnk_source:
description: The target of the symlink normalized for the remote filesystem.
returned: success, path exists, path is a symbolic link or junction point
type: str
sample: C:\temp
lnk_target:
description: The target of the symlink. Note that relative paths remain relative, will return null if not a link.
returned: success, path exists, path is a symbolic link or junction point
type: str
sample: temp
nlink:
description: Number of links to the file (hard links)
returned: success, path exists
type: int
sample: 1
owner:
description: The owner of the file.
returned: success, path exists
type: str
sample: BUILTIN\Administrators
path:
description: The full absolute path to the file.
returned: success, path exists
type: str
sample: BUILTIN\Administrators
sharename:
description: The name of share if folder is shared.
returned: success, path exists, path is a directory and isshared == True
type: str
sample: file-share
size:
description: The size in bytes of the file.
returned: success, path exists, path is a file
type: int
sample: 1024
'''

@ -1,277 +0,0 @@
#!powershell
# Copyright: (c) 2015, Paul Durivage <paul.durivage@rackspace.com>
# Copyright: (c) 2015, Tal Auslander <tal@cloudshare.com>
# Copyright: (c) 2017, Dag Wieers <dag@wieers.com>
# Copyright: (c) 2019, Viktor Utkin <viktor_utkin@epam.com>
# Copyright: (c) 2019, Uladzimir Klybik <uladzimir_klybik@epam.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.FileUtil
#Requires -Module Ansible.ModuleUtils.WebRequest
$spec = @{
options = @{
url = @{ type="str"; required=$true }
dest = @{ type='path'; required=$true }
force = @{ type='bool'; default=$true }
checksum = @{ type='str' }
checksum_algorithm = @{ type='str'; default='sha1'; choices = @("md5", "sha1", "sha256", "sha384", "sha512") }
checksum_url = @{ type='str' }
# Defined for the alias backwards compatibility, remove once aliases are removed
url_username = @{
aliases = @("user", "username")
deprecated_aliases = @(
@{ name = "user"; version = "2.14" },
@{ name = "username"; version = "2.14" }
)
}
url_password = @{
aliases = @("password")
deprecated_aliases = @(
@{ name = "password"; version = "2.14" }
)
}
}
mutually_exclusive = @(
,@('checksum', 'checksum_url')
)
supports_check_mode = $true
}
$spec = Merge-WebRequestSpec -ModuleSpec $spec
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$url = $module.Params.url
$dest = $module.Params.dest
$force = $module.Params.force
$checksum = $module.Params.checksum
$checksum_algorithm = $module.Params.checksum_algorithm
$checksum_url = $module.Params.checksum_url
$module.Result.elapsed = 0
$module.Result.url = $url
Function Get-ChecksumFromUri {
param(
[Parameter(Mandatory=$true)][Ansible.Basic.AnsibleModule]$Module,
[Parameter(Mandatory=$true)][Uri]$Uri,
[Uri]$SourceUri
)
$script = {
param($Response, $Stream)
$read_stream = New-Object -TypeName System.IO.StreamReader -ArgumentList $Stream
$web_checksum = $read_stream.ReadToEnd()
$basename = (Split-Path -Path $SourceUri.LocalPath -Leaf)
$basename = [regex]::Escape($basename)
$web_checksum_str = $web_checksum -split '\r?\n' | Select-String -Pattern $("\s+\.?\/?\\?" + $basename + "\s*$")
if (-not $web_checksum_str) {
$Module.FailJson("Checksum record not found for file name '$basename' in file from url: '$Uri'")
}
$web_checksum_str_splitted = $web_checksum_str[0].ToString().split(" ", 2)
$hash_from_file = $web_checksum_str_splitted[0].Trim()
# Remove any non-alphanumeric characters
$hash_from_file = $hash_from_file -replace '\W+', ''
Write-Output -InputObject $hash_from_file
}
$web_request = Get-AnsibleWebRequest -Uri $Uri -Module $Module
try {
Invoke-WithWebRequest -Module $Module -Request $web_request -Script $script
} catch {
$Module.FailJson("Error when getting the remote checksum from '$Uri'. $($_.Exception.Message)", $_)
}
}
Function Compare-ModifiedFile {
<#
.SYNOPSIS
Compares the remote URI resource against the local Dest resource. Will
return true if the LastWriteTime/LastModificationDate of the remote is
newer than the local resource date.
#>
param(
[Parameter(Mandatory=$true)][Ansible.Basic.AnsibleModule]$Module,
[Parameter(Mandatory=$true)][Uri]$Uri,
[Parameter(Mandatory=$true)][String]$Dest
)
$dest_last_mod = (Get-AnsibleItem -Path $Dest).LastWriteTimeUtc
# If the URI is a file we don't need to go through the whole WebRequest
if ($Uri.IsFile) {
$src_last_mod = (Get-AnsibleItem -Path $Uri.AbsolutePath).LastWriteTimeUtc
} else {
$web_request = Get-AnsibleWebRequest -Uri $Uri -Module $Module
$web_request.Method = switch ($web_request.GetType().Name) {
FtpWebRequest { [System.Net.WebRequestMethods+Ftp]::GetDateTimestamp }
HttpWebRequest { [System.Net.WebRequestMethods+Http]::Head }
}
$script = { param($Response, $Stream); $Response.LastModified }
try {
$src_last_mod = Invoke-WithWebRequest -Module $Module -Request $web_request -Script $script
} catch {
$Module.FailJson("Error when requesting 'Last-Modified' date from '$Uri'. $($_.Exception.Message)", $_)
}
}
# Return $true if the Uri LastModification date is newer than the Dest LastModification date
((Get-Date -Date $src_last_mod).ToUniversalTime() -gt $dest_last_mod)
}
Function Get-Checksum {
param(
[Parameter(Mandatory=$true)][String]$Path,
[String]$Algorithm = "sha1"
)
switch ($Algorithm) {
'md5' { $sp = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider }
'sha1' { $sp = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider }
'sha256' { $sp = New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider }
'sha384' { $sp = New-Object -TypeName System.Security.Cryptography.SHA384CryptoServiceProvider }
'sha512' { $sp = New-Object -TypeName System.Security.Cryptography.SHA512CryptoServiceProvider }
}
$fs = [System.IO.File]::Open($Path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read,
[System.IO.FileShare]::ReadWrite)
try {
$hash = [System.BitConverter]::ToString($sp.ComputeHash($fs)).Replace("-", "").ToLower()
} finally {
$fs.Dispose()
}
return $hash
}
Function Invoke-DownloadFile {
param(
[Parameter(Mandatory=$true)][Ansible.Basic.AnsibleModule]$Module,
[Parameter(Mandatory=$true)][Uri]$Uri,
[Parameter(Mandatory=$true)][String]$Dest,
[String]$Checksum,
[String]$ChecksumAlgorithm
)
# Check $dest parent folder exists before attempting download, which avoids unhelpful generic error message.
$dest_parent = Split-Path -LiteralPath $Dest
if (-not (Test-Path -LiteralPath $dest_parent -PathType Container)) {
$module.FailJson("The path '$dest_parent' does not exist for destination '$Dest', or is not visible to the current user. Ensure download destination folder exists (perhaps using win_file state=directory) before win_get_url runs.")
}
$download_script = {
param($Response, $Stream)
# Download the file to a temporary directory so we can compare it
$tmp_dest = Join-Path -Path $Module.Tmpdir -ChildPath ([System.IO.Path]::GetRandomFileName())
$fs = [System.IO.File]::Create($tmp_dest)
try {
$Stream.CopyTo($fs)
$fs.Flush()
} finally {
$fs.Dispose()
}
$tmp_checksum = Get-Checksum -Path $tmp_dest -Algorithm $ChecksumAlgorithm
$Module.Result.checksum_src = $tmp_checksum
# If the checksum has been set, verify the checksum of the remote against the input checksum.
if ($Checksum -and $Checksum -ne $tmp_checksum) {
$Module.FailJson(("The checksum for {0} did not match '{1}', it was '{2}'" -f $Uri, $Checksum, $tmp_checksum))
}
$download = $true
if (Test-Path -LiteralPath $Dest) {
# Validate the remote checksum against the existing downloaded file
$dest_checksum = Get-Checksum -Path $Dest -Algorithm $ChecksumAlgorithm
# If we don't need to download anything, save the dest checksum so we don't waste time calculating it
# again at the end of the script
if ($dest_checksum -eq $tmp_checksum) {
$download = $false
$Module.Result.checksum_dest = $dest_checksum
$Module.Result.size = (Get-AnsibleItem -Path $Dest).Length
}
}
if ($download) {
Copy-Item -LiteralPath $tmp_dest -Destination $Dest -Force -WhatIf:$Module.CheckMode > $null
$Module.Result.changed = $true
}
}
$web_request = Get-AnsibleWebRequest -Uri $Uri -Module $Module
try {
Invoke-WithWebRequest -Module $Module -Request $web_request -Script $download_script
} catch {
$Module.FailJson("Error downloading '$Uri' to '$Dest': $($_.Exception.Message)", $_)
}
}
# Use last part of url for dest file name if a directory is supplied for $dest
if (Test-Path -LiteralPath $dest -PathType Container) {
$uri = [System.Uri]$url
$basename = Split-Path -Path $uri.LocalPath -Leaf
if ($uri.LocalPath -and $uri.LocalPath -ne '/' -and $basename) {
$url_basename = Split-Path -Path $uri.LocalPath -Leaf
$dest = Join-Path -Path $dest -ChildPath $url_basename
} else {
$dest = Join-Path -Path $dest -ChildPath $uri.Host
}
# Ensure we have a string instead of a PS object to avoid serialization issues
$dest = $dest.ToString()
} elseif (([System.IO.Path]::GetFileName($dest)) -eq '') {
# We have a trailing path separator
$module.FailJson("The destination path '$dest' does not exist, or is not visible to the current user. Ensure download destination folder exists (perhaps using win_file state=directory) before win_get_url runs.")
}
$module.Result.dest = $dest
if ($checksum) {
$checksum = $checksum.Trim().ToLower()
}
if ($checksum_algorithm) {
$checksum_algorithm = $checksum_algorithm.Trim().ToLower()
}
if ($checksum_url) {
$checksum_url = $checksum_url.Trim()
}
# Check for case $checksum variable contain url. If yes, get file data from url and replace original value in $checksum
if ($checksum_url) {
$checksum_uri = [System.Uri]$checksum_url
if ($checksum_uri.Scheme -notin @("file", "ftp", "http", "https")) {
$module.FailJson("Unsupported 'checksum_url' value for '$dest': '$checksum_url'")
}
$checksum = Get-ChecksumFromUri -Module $Module -Uri $checksum_uri -SourceUri $url
}
if ($force -or -not (Test-Path -LiteralPath $dest)) {
# force=yes or dest does not exist, download the file
# Note: Invoke-DownloadFile will compare the checksums internally if dest exists
Invoke-DownloadFile -Module $module -Uri $url -Dest $dest -Checksum $checksum `
-ChecksumAlgorithm $checksum_algorithm
} else {
# force=no, we want to check the last modified dates and only download if they don't match
$is_modified = Compare-ModifiedFile -Module $module -Uri $url -Dest $dest
if ($is_modified) {
Invoke-DownloadFile -Module $module -Uri $url -Dest $dest -Checksum $checksum `
-ChecksumAlgorithm $checksum_algorithm
}
}
if ((-not $module.Result.ContainsKey("checksum_dest")) -and (Test-Path -LiteralPath $dest)) {
# Calculate the dest file checksum if it hasn't already been done
$module.Result.checksum_dest = Get-Checksum -Path $dest -Algorithm $checksum_algorithm
$module.Result.size = (Get-AnsibleItem -Path $dest).Length
}
$module.ExitJson()

@ -1,215 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2014, Paul Durivage <paul.durivage@rackspace.com>, and others
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# This is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_get_url
version_added: "1.7"
short_description: Downloads file from HTTP, HTTPS, or FTP to node
description:
- Downloads files from HTTP, HTTPS, or FTP to the remote server.
- The remote server I(must) have direct access to the remote resource.
- For non-Windows targets, use the M(get_url) module instead.
options:
url:
description:
- The full URL of a file to download.
type: str
required: yes
dest:
description:
- The location to save the file at the URL.
- Be sure to include a filename and extension as appropriate.
type: path
required: yes
force:
description:
- If C(yes), will download the file every time and replace the file if the contents change. If C(no), will only
download the file if it does not exist or the remote file has been
modified more recently than the local file.
- This works by sending an http HEAD request to retrieve last modified
time of the requested resource, so for this to work, the remote web
server must support HEAD requests.
type: bool
default: yes
version_added: "2.0"
checksum:
description:
- If a I(checksum) is passed to this parameter, the digest of the
destination file will be calculated after it is downloaded to ensure
its integrity and verify that the transfer completed successfully.
- This option cannot be set with I(checksum_url).
type: str
version_added: "2.8"
checksum_algorithm:
description:
- Specifies the hashing algorithm used when calculating the checksum of
the remote and destination file.
type: str
choices:
- md5
- sha1
- sha256
- sha384
- sha512
default: sha1
version_added: "2.8"
checksum_url:
description:
- Specifies a URL that contains the checksum values for the resource at
I(url).
- Like C(checksum), this is used to verify the integrity of the remote
transfer.
- This option cannot be set with I(checksum).
type: str
version_added: "2.8"
url_username:
description:
- The username to use for authentication.
- The aliases I(user) and I(username) are deprecated and will be removed in
Ansible 2.14.
aliases:
- user
- username
url_password:
description:
- The password for I(url_username).
- The alias I(password) is deprecated and will be removed in Ansible 2.14.
aliases:
- password
proxy_url:
version_added: "2.0"
proxy_username:
version_added: "2.0"
proxy_password:
version_added: "2.0"
headers:
version_added: "2.4"
use_proxy:
version_added: "2.4"
follow_redirects:
version_added: "2.9"
maximum_redirection:
version_added: "2.9"
client_cert:
version_added: "2.9"
client_cert_password:
version_added: "2.9"
method:
description:
- This option is not for use with C(win_get_url) and should be ignored.
version_added: "2.9"
notes:
- If your URL includes an escaped slash character (%2F) this module will convert it to a real slash.
This is a result of the behaviour of the System.Uri class as described in
L(the documentation,https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/network/schemesettings-element-uri-settings#remarks).
- Since Ansible 2.8, the module will skip reporting a change if the remote
checksum is the same as the local local even when C(force=yes). This is to
better align with M(get_url).
extends_documentation_fragment:
- url_windows
seealso:
- module: get_url
- module: uri
- module: win_uri
author:
- Paul Durivage (@angstwad)
- Takeshi Kuramochi (@tksarah)
'''
EXAMPLES = r'''
- name: Download earthrise.jpg to specified path
win_get_url:
url: http://www.example.com/earthrise.jpg
dest: C:\Users\RandomUser\earthrise.jpg
- name: Download earthrise.jpg to specified path only if modified
win_get_url:
url: http://www.example.com/earthrise.jpg
dest: C:\Users\RandomUser\earthrise.jpg
force: no
- name: Download earthrise.jpg to specified path through a proxy server.
win_get_url:
url: http://www.example.com/earthrise.jpg
dest: C:\Users\RandomUser\earthrise.jpg
proxy_url: http://10.0.0.1:8080
proxy_username: username
proxy_password: password
- name: Download file from FTP with authentication
win_get_url:
url: ftp://server/file.txt
dest: '%TEMP%\ftp-file.txt'
url_username: ftp-user
url_password: ftp-password
- name: Download src with sha256 checksum url
win_get_url:
url: http://www.example.com/earthrise.jpg
dest: C:\temp\earthrise.jpg
checksum_url: http://www.example.com/sha256sum.txt
checksum_algorithm: sha256
force: True
- name: Download src with sha256 checksum url
win_get_url:
url: http://www.example.com/earthrise.jpg
dest: C:\temp\earthrise.jpg
checksum: a97e6837f60cec6da4491bab387296bbcd72bdba
checksum_algorithm: sha1
force: True
'''
RETURN = r'''
dest:
description: destination file/path
returned: always
type: str
sample: C:\Users\RandomUser\earthrise.jpg
checksum_dest:
description: <algorithm> checksum of the file after the download
returned: success and dest has been downloaded
type: str
sample: 6e642bb8dd5c2e027bf21dd923337cbb4214f827
checksum_src:
description: <algorithm> checksum of the remote resource
returned: force=yes or dest did not exist
type: str
sample: 6e642bb8dd5c2e027bf21dd923337cbb4214f827
elapsed:
description: The elapsed seconds between the start of poll and the end of the module.
returned: always
type: float
sample: 2.1406487
size:
description: size of the dest file
returned: success
type: int
sample: 1220
url:
description: requested url
returned: always
type: str
sample: http://www.example.com/earthrise.jpg
msg:
description: Error message, or HTTP status message from web-server
returned: always
type: str
sample: OK
status_code:
description: HTTP status code
returned: always
type: int
sample: 200
'''

@ -1,54 +0,0 @@
#!powershell
# Copyright: (c) 2014, Chris Hoffman <choffman@chathamfinancial.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent"
$description = Get-AnsibleParam -obj $params -name "description" -type "str"
$result = @{
changed = $false
}
$adsi = [ADSI]"WinNT://$env:COMPUTERNAME"
$group = $adsi.Children | Where-Object {$_.SchemaClassName -eq 'group' -and $_.Name -eq $name }
try {
If ($state -eq "present") {
If (-not $group) {
If (-not $check_mode) {
$group = $adsi.Create("Group", $name)
$group.SetInfo()
}
$result.changed = $true
}
If ($null -ne $description) {
IF (-not $group.description -or $group.description -ne $description) {
$group.description = $description
If (-not $check_mode) {
$group.SetInfo()
}
$result.changed = $true
}
}
}
ElseIf ($state -eq "absent" -and $group) {
If (-not $check_mode) {
$adsi.delete("Group", $group.Name.Value)
}
$result.changed = $true
}
}
catch {
Fail-Json $result $_.Exception.Message
}
Exit-Json $result

@ -1,57 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2014, Chris Hoffman <choffman@chathamfinancial.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_group
version_added: "1.7"
short_description: Add and remove local groups
description:
- Add and remove local groups.
- For non-Windows targets, please use the M(group) module instead.
options:
name:
description:
- Name of the group.
type: str
required: yes
description:
description:
- Description of the group.
type: str
state:
description:
- Create or remove the group.
type: str
choices: [ absent, present ]
default: present
seealso:
- module: group
- module: win_domain_group
- module: win_group_membership
author:
- Chris Hoffman (@chrishoffman)
'''
EXAMPLES = r'''
- name: Create a new group
win_group:
name: deploy
description: Deploy Group
state: present
- name: Remove a group
win_group:
name: deploy
state: absent
'''

@ -1,190 +0,0 @@
#!powershell
# Copyright: (c) 2017, Andrew Saraceni <andrew.saraceni@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.SID
$ErrorActionPreference = "Stop"
function Test-GroupMember {
<#
.SYNOPSIS
Return SID and consistent account name (DOMAIN\Username) format of desired member.
Also, ensure member can be resolved/exists on the target system by checking its SID.
.NOTES
Returns a hashtable of the same type as returned from Get-GroupMember.
Accepts username (users, groups) and domains in the formats accepted by Convert-ToSID.
#>
param(
[String]$GroupMember
)
$parsed_member = @{
sid = $null
account_name = $null
}
$sid = Convert-ToSID -account_name $GroupMember
$account_name = Convert-FromSID -sid $sid
$parsed_member.sid = $sid
$parsed_member.account_name = $account_name
return $parsed_member
}
function Get-GroupMember {
<#
.SYNOPSIS
Retrieve group members for a given group, and return in a common format.
.NOTES
Returns an array of hashtables of the same type as returned from Test-GroupMember.
#>
param(
[System.DirectoryServices.DirectoryEntry]$Group
)
# instead of using ForEach pipeline we use a standard loop and cast the
# object to the ADSI adapter type before using it to get the SID and path
# this solves an random issue where multiple casts could fail once the raw
# object is invoked at least once
$raw_members = $Group.psbase.Invoke("Members")
$current_members = [System.Collections.ArrayList]@()
foreach ($raw_member in $raw_members) {
$raw_member = [ADSI]$raw_member
$sid_bytes = $raw_member.InvokeGet("objectSID")
$ads_path = $raw_member.InvokeGet("ADsPath")
$member_info = @{
sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $sid_bytes, 0
adspath = $ads_path
}
$current_members.Add($member_info) > $null
}
$members = @()
foreach ($current_member in $current_members) {
$parsed_member = @{
sid = $current_member.sid
account_name = $null
}
$rootless_adspath = $current_member.adspath.Replace("WinNT://", "")
$split_adspath = $rootless_adspath.Split("/")
# Ignore lookup on a broken SID, and just return the SID as the account_name
if ($split_adspath.Count -eq 1 -and $split_adspath[0] -like "S-1*") {
$parsed_member.account_name = $split_adspath[0]
} else {
$account_name = Convert-FromSID -sid $current_member.sid
$parsed_member.account_name = $account_name
}
$members += $parsed_member
}
return $members
}
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$members = Get-AnsibleParam -obj $params -name "members" -type "list" -failifempty $true
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","pure"
$result = @{
changed = $false
name = $name
}
if ($state -in @("present", "pure")) {
$result.added = @()
}
if ($state -in @("absent", "pure")) {
$result.removed = @()
}
$adsi = [ADSI]"WinNT://$env:COMPUTERNAME"
$group = $adsi.Children | Where-Object { $_.SchemaClassName -eq "group" -and $_.Name -eq $name }
if (!$group) {
Fail-Json -obj $result -message "Could not find local group $name"
}
$current_members = Get-GroupMember -Group $group
$pure_members = @()
foreach ($member in $members) {
$group_member = Test-GroupMember -GroupMember $member
if ($state -eq "pure") {
$pure_members += $group_member
}
$user_in_group = $false
foreach ($current_member in $current_members) {
if ($current_member.sid -eq $group_member.sid) {
$user_in_group = $true
break
}
}
$member_sid = "WinNT://{0}" -f $group_member.sid
try {
if ($state -in @("present", "pure") -and !$user_in_group) {
if (!$check_mode) {
$group.Add($member_sid)
$result.added += $group_member.account_name
}
$result.changed = $true
} elseif ($state -eq "absent" -and $user_in_group) {
if (!$check_mode) {
$group.Remove($member_sid)
$result.removed += $group_member.account_name
}
$result.changed = $true
}
} catch {
Fail-Json -obj $result -message $_.Exception.Message
}
}
if ($state -eq "pure") {
# Perform removals for existing group members not defined in $members
$current_members = Get-GroupMember -Group $group
foreach ($current_member in $current_members) {
$user_to_remove = $true
foreach ($pure_member in $pure_members) {
if ($pure_member.sid -eq $current_member.sid) {
$user_to_remove = $false
break
}
}
$member_sid = "WinNT://{0}" -f $current_member.sid
try {
if ($user_to_remove) {
if (!$check_mode) {
$group.Remove($member_sid)
$result.removed += $current_member.account_name
}
$result.changed = $true
}
} catch {
Fail-Json -obj $result -message $_.Exception.Message
}
}
}
$final_members = Get-GroupMember -Group $group
if ($final_members) {
$result.members = [Array]$final_members.account_name
} else {
$result.members = @()
}
Exit-Json -obj $result

@ -1,101 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Andrew Saraceni <andrew.saraceni@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_group_membership
version_added: "2.4"
short_description: Manage Windows local group membership
description:
- Allows the addition and removal of local, service and domain users,
and domain groups from a local group.
options:
name:
description:
- Name of the local group to manage membership on.
type: str
required: yes
members:
description:
- A list of members to ensure are present/absent from the group.
- Accepts local users as .\username, and SERVERNAME\username.
- Accepts domain users and groups as DOMAIN\username and username@DOMAIN.
- Accepts service users as NT AUTHORITY\username.
- Accepts all local, domain and service user types as username,
favoring domain lookups when in a domain.
type: list
required: yes
state:
description:
- Desired state of the members in the group.
- C(pure) was added in Ansible 2.8.
- When C(state) is C(pure), only the members specified will exist,
and all other existing members not specified are removed.
type: str
choices: [ absent, present, pure ]
default: present
seealso:
- module: win_domain_group
- module: win_domain_membership
- module: win_group
author:
- Andrew Saraceni (@andrewsaraceni)
'''
EXAMPLES = r'''
- name: Add a local and domain user to a local group
win_group_membership:
name: Remote Desktop Users
members:
- NewLocalAdmin
- DOMAIN\TestUser
state: present
- name: Remove a domain group and service user from a local group
win_group_membership:
name: Backup Operators
members:
- DOMAIN\TestGroup
- NT AUTHORITY\SYSTEM
state: absent
- name: Ensure only a domain user exists in a local group
win_group_membership:
name: Remote Desktop Users
members:
- DOMAIN\TestUser
state: pure
'''
RETURN = r'''
name:
description: The name of the target local group.
returned: always
type: str
sample: Administrators
added:
description: A list of members added when C(state) is C(present) or
C(pure); this is empty if no members are added.
returned: success and C(state) is C(present)
type: list
sample: ["SERVERNAME\\NewLocalAdmin", "DOMAIN\\TestUser"]
removed:
description: A list of members removed when C(state) is C(absent) or
C(pure); this is empty if no members are removed.
returned: success and C(state) is C(absent)
type: list
sample: ["DOMAIN\\TestGroup", "NT AUTHORITY\\SYSTEM"]
members:
description: A list of all local group members at completion; this is empty
if the group contains no members.
returned: success
type: list
sample: ["DOMAIN\\TestUser", "SERVERNAME\\NewLocalAdmin"]
'''

@ -1,32 +0,0 @@
#!powershell
# Copyright: (c) 2018, Ripon Banik (@riponbanik)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
$ErrorActionPreference = "Stop"
$params = Parse-Args $args -supports_check_mode $true
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$current_computer_name = (Get-CimInstance -Class Win32_ComputerSystem).DNSHostname
$result = @{
changed = $false
old_name = $current_computer_name
reboot_required = $false
}
if ($name -ne $current_computer_name) {
Try {
Rename-Computer -NewName $name -Force -WhatIf:$check_mode
} Catch {
Fail-Json -obj $result -message "Failed to rename computer to '$name': $($_.Exception.Message)"
}
$result.changed = $true
$result.reboot_required = $true
}
Exit-Json -obj $result

@ -1,55 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
# Copyright: (c) 2018, Ripon Banik (@riponbanik)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
module: win_hostname
version_added: "2.6"
short_description: Manages local Windows computer name
description:
- Manages local Windows computer name.
- A reboot is required for the computer name to take effect.
options:
name:
description:
- The hostname to set for the computer.
type: str
required: true
seealso:
- module: win_dns_client
author:
- Ripon Banik (@riponbanik)
'''
EXAMPLES = r'''
- name: Change the hostname to sample-hostname
win_hostname:
name: sample-hostname
register: res
- name: Reboot
win_reboot:
when: res.reboot_required
'''
RETURN = r'''
old_name:
description: The original hostname that was set before it was changed.
returned: always
type: str
sample: old_hostname
reboot_required:
description: Whether a reboot is required to complete the hostname change.
returned: always
type: bool
sample: true
'''

@ -1,83 +0,0 @@
#!powershell
# Copyright: (c) 2019, Carson Anderson <rcanderson23@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
$spec = @{
options = @{
name = @{ type = "list"; required = $true }
state = @{ type = "str"; default = "present"; choices = @("absent", "present") }
source = @{ type = "str" }
include_parent = @{ type = "bool"; default = $false }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$name = $module.Params.name
$state = $module.Params.state
$source = $module.Params.source
$include_parent = $module.Params.include_parent
$module.Result.reboot_required = $false
if (-not (Get-Command -Name Enable-WindowsOptionalFeature -ErrorAction SilentlyContinue)) {
$module.FailJson("This version of Windows does not support the Enable-WindowsOptionalFeature.")
}
$changed_features = [System.Collections.Generic.List`1[String]]@()
foreach ($feature_name in $name) {
try {
$feature_state_start = Get-WindowsOptionalFeature -Online -FeatureName $feature_name
} catch [System.Runtime.InteropServices.COMException] {
# Server 2012 raises a COMException and doesn't return $null even with -ErrorAction SilentlyContinue
$feature_state_start = $null
}
if (-not $feature_state_start) {
$module.FailJson("Failed to find feature '$feature_name'")
}
if ($state -eq "present" -and $feature_state_start.State -notlike "Enabled*") {
# Matches for "Enabled" and "EnabledPending"
$changed_features.Add($feature_name)
} elseif ($state -eq "absent" -and $feature_state_start.State -notlike "Disabled*") {
# Matches for Disabled, DisabledPending, and DisabledWithPayloadRemoved
$changed_features.Add($feature_name)
}
}
if ($state -eq "present" -and $changed_features.Count -gt 0) {
$install_args = @{
FeatureName = $changed_features
All = $include_parent
}
if ($source) {
if (-not (Test-Path -LiteralPath $source)) {
$module.FailJson("Path could not be found '$source'")
}
$install_args.Source = $source
}
if (-not $module.CheckMode) {
$action_result = Enable-WindowsOptionalFeature -Online -NoRestart @install_args
$module.Result.reboot_required = $action_result.RestartNeeded
}
$module.Result.changed = $true
} elseif ($state -eq "absent" -and $changed_features.Count -gt 0) {
$remove_args = @{
FeatureName = $changed_features
}
if (-not $module.CheckMode) {
$action_result = Disable-WindowsOptionalFeature -Online -NoRestart @remove_args
$module.Result.reboot_required = $action_result.RestartNeeded
}
$module.Result.changed = $true
}
$module.ExitJson()

@ -1,90 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Carson Anderson <rcanderson23@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_optional_feature
version_added: "2.8"
short_description: Manage optional Windows features
description:
- Install or uninstall optional Windows features on non-Server Windows.
- This module uses the C(Enable-WindowsOptionalFeature) and C(Disable-WindowsOptionalFeature) cmdlets.
options:
name:
description:
- The name(s) of the feature to install.
- This relates to C(FeatureName) in the Powershell cmdlet.
- To list all available features use the PowerShell command C(Get-WindowsOptionalFeature).
type: list
required: yes
state:
description:
- Whether to ensure the feature is absent or present on the system.
type: str
choices: [ absent, present ]
default: present
include_parent:
description:
- Whether to enable the parent feature and the parent's dependencies.
type: bool
default: no
source:
description:
- Specify a source to install the feature from.
- Can either be C({driveletter}:\sources\sxs) or C(\\{IP}\share\sources\sxs).
type: str
seealso:
- module: win_chocolatey
- module: win_feature
- module: win_package
author:
- Carson Anderson (@rcanderson23)
'''
EXAMPLES = r'''
- name: Install .Net 3.5
win_optional_feature:
name: NetFx3
state: present
- name: Install .Net 3.5 from source
win_optional_feature:
name: NetFx3
source: \\share01\win10\sources\sxs
state: present
- name: Install Microsoft Subsystem for Linux
win_optional_feature:
name: Microsoft-Windows-Subsystem-Linux
state: present
register: wsl_status
- name: Reboot if installing Linux Subsytem as feature requires it
win_reboot:
when: wsl_status.reboot_required
- name: Install multiple features in one task
win_optional_feature:
name:
- NetFx3
- Microsoft-Windows-Subsystem-Linux
state: present
'''
RETURN = r'''
reboot_required:
description: True when the target server requires a reboot to complete updates
returned: success
type: bool
sample: true
'''

@ -1,60 +0,0 @@
#!powershell
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.SID
$result = @{
changed = $false
}
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true
$user = Get-AnsibleParam -obj $params -name "user" -type "str" -failifempty $true
$recurse = Get-AnsibleParam -obj $params -name "recurse" -type "bool" -default $false -resultobj $result
If (-Not (Test-Path -LiteralPath $path)) {
Fail-Json $result "$path file or directory does not exist on the host"
}
# Test that the user/group is resolvable on the local machine
$sid = Convert-ToSID -account_name $user
if (!$sid) {
Fail-Json $result "$user is not a valid user or group on the host machine or domain"
}
Try {
$objUser = New-Object System.Security.Principal.SecurityIdentifier($sid)
$file = Get-Item -LiteralPath $path
$acl = Get-Acl -LiteralPath $file.FullName
If ($acl.getOwner([System.Security.Principal.SecurityIdentifier]) -ne $objUser) {
$acl.setOwner($objUser)
Set-Acl -LiteralPath $file.FullName -AclObject $acl -WhatIf:$check_mode
$result.changed = $true
}
If ($recurse -and $file -is [System.IO.DirectoryInfo]) {
# Get-ChildItem falls flat on pre PSv5 when dealing with complex path chars
$files = $file.EnumerateFileSystemInfos("*", [System.IO.SearchOption]::AllDirectories)
ForEach($file in $files){
$acl = Get-Acl -LiteralPath $file.FullName
If ($acl.getOwner([System.Security.Principal.SecurityIdentifier]) -ne $objUser) {
$acl.setOwner($objUser)
Set-Acl -LiteralPath $file.FullName -AclObject $acl -WhatIf:$check_mode
$result.changed = $true
}
}
}
}
Catch {
Fail-Json $result "an error occurred when attempting to change owner on $path for $($user): $($_.Exception.Message)"
}
Exit-Json $result

@ -1,58 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_owner
version_added: "2.1"
short_description: Set owner
description:
- Set owner of files or directories.
options:
path:
description:
- Path to be used for changing owner.
type: path
required: yes
user:
description:
- Name to be used for changing owner.
type: str
required: yes
recurse:
description:
- Indicates if the owner should be changed recursively.
type: bool
default: no
seealso:
- module: win_acl
- module: win_file
- module: win_stat
author:
- Hans-Joachim Kliemeck (@h0nIg)
'''
EXAMPLES = r'''
- name: Change owner of path
win_owner:
path: C:\apache
user: apache
recurse: yes
- name: Set the owner of root directory
win_owner:
path: C:\apache
user: SYSTEM
recurse: no
'''
RETURN = r'''
'''

File diff suppressed because it is too large Load Diff

@ -1,386 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2014, Trond Hindenes <trond@hindenes.com>, and others
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_package
version_added: "1.7"
short_description: Installs/uninstalls an installable package
description:
- Installs or uninstalls software packages for Windows.
- Supports C(.exe), C(.msi), C(.msp), C(.appx), C(.appxbundle), C(.msix),
and C(.msixbundle).
- These packages can be sourced from the local file system, network file share
or a url.
- See I(provider) for more info on each package type that is supported.
options:
arguments:
description:
- Any arguments the installer needs to either install or uninstall the
package.
- If the package is an MSI do not supply the C(/qn), C(/log) or
C(/norestart) arguments.
- This is only used for the C(msi), C(msp), and C(registry) providers.
- As of Ansible 2.5, this parameter can be a list of arguments and the
module will escape the arguments as necessary, it is recommended to use a
string when dealing with MSI packages due to the unique escaping issues
with msiexec.
type: raw
chdir:
description:
- Set the specified path as the current working directory before installing
or uninstalling a package.
- This is only used for the C(msi), C(msp), and C(registry) providers.
type: path
version_added: '2.8'
creates_path:
description:
- Will check the existence of the path specified and use the result to
determine whether the package is already installed.
- You can use this in conjunction with C(product_id) and other C(creates_*).
type: path
version_added: '2.4'
creates_service:
description:
- Will check the existing of the service specified and use the result to
determine whether the package is already installed.
- You can use this in conjunction with C(product_id) and other C(creates_*).
type: str
version_added: '2.4'
creates_version:
description:
- Will check the file version property of the file at C(creates_path) and
use the result to determine whether the package is already installed.
- C(creates_path) MUST be set and is a file.
- You can use this in conjunction with C(product_id) and other C(creates_*).
type: str
version_added: '2.4'
expected_return_code:
description:
- One or more return codes from the package installation that indicates
success.
- Before Ansible 2.4 this was just 0 but since Ansible 2.4 this is both C(0) and
C(3010).
- A return code of C(3010) usually means that a reboot is required, the
C(reboot_required) return value is set if the return code is C(3010).
- This is only used for the C(msi), C(msp), and C(registry) providers.
type: list
elements: int
default: [0, 3010]
log_path:
description:
- Specifies the path to a log file that is persisted after a package is
installed or uninstalled.
- This is only used for the C(msi) or C(msp) provider.
- When omitted, a temporary log file is used instead for those providers.
- This is only valid for MSI files, use C(arguments) for the C(registry)
provider.
type: path
version_added: '2.8'
password:
description:
- The password for C(user_name), must be set when C(user_name) is.
- This option is deprecated in favour of using become, see examples for
more information.
type: str
aliases: [ user_password ]
path:
description:
- Location of the package to be installed or uninstalled.
- This package can either be on the local file system, network share or a
url.
- When C(state=present), C(product_id) is not set and the path is a URL,
this file will always be downloaded to a temporary directory for
idempotency checks, otherwise the file will only be downloaded if the
package has not been installed based on the C(product_id) checks.
- If C(state=present) then this value MUST be set.
- If C(state=absent) then this value does not need to be set if
C(product_id) is.
type: str
product_id:
description:
- The product id of the installed packaged.
- This is used for checking whether the product is already installed and
getting the uninstall information if C(state=absent).
- For msi packages, this is the C(ProductCode) (GUID) of the package. This
can be found under the same registry paths as the C(registry) provider.
- For msp packages, this is the C(PatchCode) (GUID) of the package which
can found under the C(Details -> Revision number) of the file's properties.
- For msix packages, this is the C(Name) or C(PackageFullName) of the
package found under the C(Get-AppxPackage) cmdlet.
- For registry (exe) packages, this is the registry key name under the
registry paths specified in I(provider).
- This value is ignored if C(path) is set to a local accesible file path
and the package is not an C(exe).
- This SHOULD be set when the package is an C(exe), or the path is a url
or a network share and credential delegation is not being used. The
C(creates_*) options can be used instead but is not recommended.
- The C(productid) alias will be removed in Ansible 2.14.
type: str
aliases: [ productid ]
provider:
description:
- Set the package provider to use when searching for a package.
- The C(auto) provider will select the proper provider if I(path)
otherwise it scans all the other providers based on the I(product_id).
- The C(msi) provider scans for MSI packages installed on a machine wide
and current user context based on the C(ProductCode) of the MSI. Before
Ansible 2.10 only the machine wide context was searched.
- The C(msix) provider is used to install C(.appx), C(.msix),
C(.appxbundle), or C(.msixbundle) packages. These packages are only
installed or removed on the current use. The host must be set to allow
sideloaded apps or in developer mode. See the examples for how to enable
this. If a package is already installed but C(path) points to an updated
package, this will be installed over the top of the existing one.
- The C(msp) provider scans for all MSP patches installed on a machine wide
and current user context based on the C(PatchCode) of the MSP. A C(msp)
will be applied or removed on all C(msi) products that it applies to and
is installed. If the patch is obsoleted or superseded then no action will
be taken.
- The C(registry) provider is used for traditional C(exe) installers and
uses the following registry path to determine if a product was installed;
C(HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall),
C(HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall),
C(HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall), and
C(HKCU:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall).
Before Ansible 2.10 only the C(HKLM) hive was searched.
- Before Ansible 2.10 only the C(msi) and C(registry) providers were used.
choices:
- auto
- msi
- msix
- msp
- registry
default: auto
type: str
version_added: '2.10'
state:
description:
- Whether to install or uninstall the package.
- The module uses I(product_id) to determine whether the package is
installed or not.
- For all providers but C(auto), the I(path) can be used for idempotency
checks if it is locally accesible filesystem path.
- The C(ensure) alias will be removed in Ansible 2.14.
type: str
choices: [ absent, present ]
default: present
aliases: [ ensure ]
username:
description:
- Username of an account with access to the package if it is located on a
file share.
- This is only needed if the WinRM transport is over an auth method that
does not support credential delegation like Basic or NTLM or become is
not used.
- This option is deprecated in favour of using become, see examples for
more information.
type: str
aliases: [ user_name ]
# Overrides the options in url_windows
client_cert:
version_added: '2.10'
client_cert_password:
version_added: '2.10'
follow_redirects:
version_added: '2.10'
force_basic_auth:
version_added: '2.10'
headers:
version_added: '2.10'
http_agent:
version_added: '2.10'
maximum_redirection:
version_added: '2.10'
method:
version_added: '2.10'
proxy_password:
version_added: '2.10'
proxy_url:
version_added: '2.10'
proxy_use_default_credential:
version_added: '2.10'
proxy_username:
version_added: '2.10'
timeout:
description:
- Specifies how long the web download request can be pending before it
times out in seconds.
- Set to C(0) to specify an infinite timeout.
version_added: '2.10'
url_password:
version_added: '2.10'
url_username:
version_added: '2.10'
use_default_credential:
version_added: '2.10'
use_proxy:
version_added: '2.10'
extends_documentation_fragment:
- url_windows
notes:
- When C(state=absent) and the product is an exe, the path may be different
from what was used to install the package originally. If path is not set then
the path used will be what is set under C(QuietUninstallString) or
C(UninstallString) in the registry for that I(product_id).
- By default all msi installs and uninstalls will be run with the arguments
C(/log, /qn, /norestart).
- All the installation checks under C(product_id) and C(creates_*) add
together, if one fails then the program is considered to be absent.
seealso:
- module: win_chocolatey
- module: win_hotfix
- module: win_updates
author:
- Trond Hindenes (@trondhindenes)
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Install the Visual C thingy
win_package:
path: http://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe
product_id: '{CF2BEA3C-26EA-32F8-AA9B-331F7E34BA97}'
arguments: /install /passive /norestart
- name: Install Visual C thingy with list of arguments instead of a string
win_package:
path: http://download.microsoft.com/download/1/6/B/16B06F60-3B20-4FF2-B699-5E9B7962F9AE/VSU_4/vcredist_x64.exe
product_id: '{CF2BEA3C-26EA-32F8-AA9B-331F7E34BA97}'
arguments:
- /install
- /passive
- /norestart
- name: Install Remote Desktop Connection Manager from msi with a permanent log
win_package:
path: https://download.microsoft.com/download/A/F/0/AF0071F3-B198-4A35-AA90-C68D103BDCCF/rdcman.msi
product_id: '{0240359E-6A4C-4884-9E94-B397A02D893C}'
state: present
log_path: D:\logs\vcredist_x64-exe-{{lookup('pipe', 'date +%Y%m%dT%H%M%S')}}.log
- name: Uninstall Remote Desktop Connection Manager
win_package:
product_id: '{0240359E-6A4C-4884-9E94-B397A02D893C}'
state: absent
- name: Install Remote Desktop Connection Manager locally omitting the product_id
win_package:
path: C:\temp\rdcman.msi
state: present
- name: Uninstall Remote Desktop Connection Manager from local MSI omitting the product_id
win_package:
path: C:\temp\rdcman.msi
state: absent
# 7-Zip exe doesn't use a guid for the Product ID
- name: Install 7zip from a network share with specific credentials
win_package:
path: \\domain\programs\7z.exe
product_id: 7-Zip
arguments: /S
state: present
become: yes
become_method: runas
become_flags: logon_type=new_credential logon_flags=netcredentials_only
vars:
ansible_become_user: DOMAIN\User
ansible_become_password: Password
- name: Install 7zip and use a file version for the installation check
win_package:
path: C:\temp\7z.exe
creates_path: C:\Program Files\7-Zip\7z.exe
creates_version: 16.04
state: present
- name: Uninstall 7zip from the exe
win_package:
path: C:\Program Files\7-Zip\Uninstall.exe
product_id: 7-Zip
arguments: /S
state: absent
- name: Uninstall 7zip without specifying the path
win_package:
product_id: 7-Zip
arguments: /S
state: absent
- name: Install application and override expected return codes
win_package:
path: https://download.microsoft.com/download/1/6/7/167F0D79-9317-48AE-AEDB-17120579F8E2/NDP451-KB2858728-x86-x64-AllOS-ENU.exe
product_id: '{7DEBE4EB-6B40-3766-BB35-5CBBC385DA37}'
arguments: '/q /norestart'
state: present
expected_return_code: [0, 666, 3010]
- name: Install a .msp patch
win_package:
path: C:\Patches\Product.msp
state: present
- name: Remove a .msp patch
win_package:
product_id: '{AC76BA86-A440-FFFF-A440-0C13154E5D00}'
state: absent
- name: Enable installation of 3rd party MSIX packages
win_regedit:
path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock
name: AllowAllTrustedApps
data: 1
type: dword
state: present
- name: Install an MSIX package for the current user
win_package:
path: C:\Installers\Calculator.msix # Can be .appx, .msixbundle, or .appxbundle
state: present
- name: Uninstall an MSIX package using the product_id
win_package:
product_id: InputApp
state: absent
'''
RETURN = r'''
log:
description: The contents of the MSI or MSP log.
returned: installation/uninstallation failure for MSI or MSP packages
type: str
sample: Installation completed successfully
rc:
description: The return code of the package process.
returned: change occurred
type: int
sample: 0
reboot_required:
description: Whether a reboot is required to finalise package. This is set
to true if the executable return code is 3010.
returned: always
type: bool
sample: true
stdout:
description: The stdout stream of the package process.
returned: failure during install or uninstall
type: str
sample: Installing program
stderr:
description: The stderr stream of the package process.
returned: failure during install or uninstall
type: str
sample: Failed to install program
'''

@ -1,145 +0,0 @@
#!powershell
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
Set-StrictMode -Version 2
$ErrorActionPreference = "Stop"
$system_path = "System\CurrentControlSet\Control\Session Manager\Environment"
$user_path = "Environment"
# list/arraylist methods don't allow IEqualityComparer override for case/backslash/quote-insensitivity, roll our own search
Function Get-IndexOfPathElement ($list, [string]$value) {
$idx = 0
$value = $value.Trim('"').Trim('\')
ForEach($el in $list) {
If ([string]$el.Trim('"').Trim('\') -ieq $value) {
return $idx
}
$idx++
}
return -1
}
# alters list in place, returns true if at least one element was added
Function Add-Elements ($existing_elements, $elements_to_add) {
$last_idx = -1
$changed = $false
ForEach($el in $elements_to_add) {
$idx = Get-IndexOfPathElement $existing_elements $el
# add missing elements at the end
If ($idx -eq -1) {
$last_idx = $existing_elements.Add($el)
$changed = $true
}
ElseIf ($idx -lt $last_idx) {
$existing_elements.RemoveAt($idx) | Out-Null
$existing_elements.Add($el) | Out-Null
$last_idx = $existing_elements.Count - 1
$changed = $true
}
Else {
$last_idx = $idx
}
}
return $changed
}
# alters list in place, returns true if at least one element was removed
Function Remove-Elements ($existing_elements, $elements_to_remove) {
$count = $existing_elements.Count
ForEach($el in $elements_to_remove) {
$idx = Get-IndexOfPathElement $existing_elements $el
$result.removed_idx = $idx
If ($idx -gt -1) {
$existing_elements.RemoveAt($idx)
}
}
return $count -ne $existing_elements.Count
}
# PS registry provider doesn't allow access to unexpanded REG_EXPAND_SZ; fall back to .NET
Function Get-RawPathVar ($scope) {
If ($scope -eq "user") {
$env_key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($user_path)
}
ElseIf ($scope -eq "machine") {
$env_key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($system_path)
}
return $env_key.GetValue($var_name, "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
}
Function Set-RawPathVar($path_value, $scope) {
If ($scope -eq "user") {
$var_path = "HKCU:\" + $user_path
}
ElseIf ($scope -eq "machine") {
$var_path = "HKLM:\" + $system_path
}
Set-ItemProperty $var_path -Name $var_name -Value $path_value -Type ExpandString | Out-Null
return $path_value
}
$parsed_args = Parse-Args $args -supports_check_mode $true
$result = @{changed=$false}
$var_name = Get-AnsibleParam $parsed_args "name" -Default "PATH"
$elements = Get-AnsibleParam $parsed_args "elements" -FailIfEmpty $result
$state = Get-AnsibleParam $parsed_args "state" -Default "present" -ValidateSet "present","absent"
$scope = Get-AnsibleParam $parsed_args "scope" -Default "machine" -ValidateSet "machine","user"
$check_mode = Get-AnsibleParam $parsed_args "_ansible_check_mode" -Default $false
If ($elements -is [string]) {
$elements = @($elements)
}
If ($elements -isnot [Array]) {
Fail-Json $result "elements must be a string or list of path strings"
}
$current_value = Get-RawPathVar $scope
$result.path_value = $current_value
# TODO: test case-canonicalization on wacky unicode values (eg turkish i)
# TODO: detect and warn/fail on unparseable path? (eg, unbalanced quotes, invalid path chars)
# TODO: detect and warn/fail if system path and Powershell isn't on it?
$existing_elements = New-Object System.Collections.ArrayList
# split on semicolons, accounting for quoted values with embedded semicolons (which may or may not be wrapped in whitespace)
$pathsplit_re = [regex] '((?<q>\s*"[^"]+"\s*)|(?<q>[^;]+))(;$|$|;)'
ForEach ($m in $pathsplit_re.Matches($current_value)) {
$existing_elements.Add($m.Groups['q'].Value) | Out-Null
}
If ($state -eq "absent") {
$result.changed = Remove-Elements $existing_elements $elements
}
ElseIf ($state -eq "present") {
$result.changed = Add-Elements $existing_elements $elements
}
# calculate the new path value from the existing elements
$path_value = [String]::Join(";", $existing_elements.ToArray())
$result.path_value = $path_value
If ($result.changed -and -not $check_mode) {
Set-RawPathVar $path_value $scope | Out-Null
}
Exit-Json $result

@ -1,79 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2016, Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# This is a windows documentation stub. Actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_path
version_added: "2.3"
short_description: Manage Windows path environment variables
description:
- Allows element-based ordering, addition, and removal of Windows path environment variables.
options:
name:
description:
- Target path environment variable name.
type: str
default: PATH
elements:
description:
- A single path element, or a list of path elements (ie, directories) to add or remove.
- When multiple elements are included in the list (and C(state) is C(present)), the elements are guaranteed to appear in the same relative order
in the resultant path value.
- Variable expansions (eg, C(%VARNAME%)) are allowed, and are stored unexpanded in the target path element.
- Any existing path elements not mentioned in C(elements) are always preserved in their current order.
- New path elements are appended to the path, and existing path elements may be moved closer to the end to satisfy the requested ordering.
- Paths are compared in a case-insensitive fashion, and trailing backslashes are ignored for comparison purposes. However, note that trailing
backslashes in YAML require quotes.
type: list
required: yes
state:
description:
- Whether the path elements specified in C(elements) should be present or absent.
type: str
choices: [ absent, present ]
scope:
description:
- The level at which the environment variable specified by C(name) should be managed (either for the current user or global machine scope).
type: str
choices: [ machine, user ]
default: machine
notes:
- This module is for modifying individual elements of path-like
environment variables. For general-purpose management of other
environment vars, use the M(win_environment) module.
- This module does not broadcast change events.
This means that the minority of windows applications which can have
their environment changed without restarting will not be notified and
therefore will need restarting to pick up new environment settings.
- User level environment variables will require an interactive user to
log out and in again before they become available.
seealso:
- module: win_environment
author:
- Matt Davis (@nitzmahone)
'''
EXAMPLES = r'''
- name: Ensure that system32 and Powershell are present on the global system path, and in the specified order
win_path:
elements:
- '%SystemRoot%\system32'
- '%SystemRoot%\system32\WindowsPowerShell\v1.0'
- name: Ensure that C:\Program Files\MyJavaThing is not on the current user's CLASSPATH
win_path:
name: CLASSPATH
elements: C:\Program Files\MyJavaThing
scope: user
state: absent
'''

@ -1,21 +0,0 @@
#!powershell
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
$spec = @{
options = @{
data = @{ type = "str"; default = "pong" }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$data = $module.Params.data
if ($data -eq "crash") {
throw "boom"
}
$module.Result.ping = $data
$module.ExitJson()

@ -1,55 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>, and others
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_ping
version_added: "1.7"
short_description: A windows version of the classic ping module
description:
- Checks management connectivity of a windows host.
- This is NOT ICMP ping, this is just a trivial test module.
- For non-Windows targets, use the M(ping) module instead.
- For Network targets, use the M(net_ping) module instead.
options:
data:
description:
- Alternate data to return instead of 'pong'.
- If this parameter is set to C(crash), the module will cause an exception.
type: str
default: pong
seealso:
- module: ping
author:
- Chris Church (@cchurch)
'''
EXAMPLES = r'''
# Test connectivity to a windows host
# ansible winserver -m win_ping
- name: Example from an Ansible Playbook
win_ping:
- name: Induce an exception to see what happens
win_ping:
data: crash
'''
RETURN = r'''
ping:
description: Value provided with the data parameter.
returned: success
type: str
sample: pong
'''

@ -1,131 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_reboot
short_description: Reboot a windows machine
description:
- Reboot a Windows machine, wait for it to go down, come back up, and respond to commands.
- For non-Windows targets, use the M(reboot) module instead.
version_added: '2.1'
options:
pre_reboot_delay:
description:
- Seconds to wait before reboot. Passed as a parameter to the reboot command.
type: int
default: 2
aliases: [ pre_reboot_delay_sec ]
post_reboot_delay:
description:
- Seconds to wait after the reboot command was successful before attempting to validate the system rebooted successfully.
- This is useful if you want wait for something to settle despite your connection already working.
type: int
default: 0
version_added: '2.4'
aliases: [ post_reboot_delay_sec ]
shutdown_timeout:
description:
- Maximum seconds to wait for shutdown to occur.
- Increase this timeout for very slow hardware, large update applications, etc.
- This option has been removed since Ansible 2.5 as the win_reboot behavior has changed.
type: int
default: 600
aliases: [ shutdown_timeout_sec ]
reboot_timeout:
description:
- Maximum seconds to wait for machine to re-appear on the network and respond to a test command.
- This timeout is evaluated separately for both reboot verification and test command success so maximum clock time is actually twice this value.
type: int
default: 600
aliases: [ reboot_timeout_sec ]
connect_timeout:
description:
- Maximum seconds to wait for a single successful TCP connection to the WinRM endpoint before trying again.
type: int
default: 5
aliases: [ connect_timeout_sec ]
test_command:
description:
- Command to expect success for to determine the machine is ready for management.
type: str
default: whoami
msg:
description:
- Message to display to users.
type: str
default: Reboot initiated by Ansible
boot_time_command:
description:
- Command to run that returns a unique string indicating the last time the system was booted.
- Setting this to a command that has different output each time it is run will cause the task to fail.
type: str
default: '(Get-WmiObject -ClassName Win32_OperatingSystem).LastBootUpTime'
version_added: '2.10'
notes:
- If a shutdown was already scheduled on the system, C(win_reboot) will abort the scheduled shutdown and enforce its own shutdown.
- Beware that when C(win_reboot) returns, the Windows system may not have settled yet and some base services could be in limbo.
This can result in unexpected behavior. Check the examples for ways to mitigate this.
- The connection user must have the C(SeRemoteShutdownPrivilege) privilege enabled, see
U(https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/force-shutdown-from-a-remote-system)
for more information.
seealso:
- module: reboot
author:
- Matt Davis (@nitzmahone)
'''
EXAMPLES = r'''
- name: Reboot the machine with all defaults
win_reboot:
- name: Reboot a slow machine that might have lots of updates to apply
win_reboot:
reboot_timeout: 3600
# Install a Windows feature and reboot if necessary
- name: Install IIS Web-Server
win_feature:
name: Web-Server
register: iis_install
- name: Reboot when Web-Server feature requires it
win_reboot:
when: iis_install.reboot_required
# One way to ensure the system is reliable, is to set WinRM to a delayed startup
- name: Ensure WinRM starts when the system has settled and is ready to work reliably
win_service:
name: WinRM
start_mode: delayed
# Additionally, you can add a delay before running the next task
- name: Reboot a machine that takes time to settle after being booted
win_reboot:
post_reboot_delay: 120
# Or you can make win_reboot validate exactly what you need to work before running the next task
- name: Validate that the netlogon service has started, before running the next task
win_reboot:
test_command: 'exit (Get-Service -Name Netlogon).Status -ne "Running"'
'''
RETURN = r'''
rebooted:
description: True if the machine was rebooted.
returned: always
type: bool
sample: true
elapsed:
description: The number of seconds that elapsed waiting for the system to be rebooted.
returned: always
type: float
sample: 23.2
'''

@ -1,126 +0,0 @@
#!powershell
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
$ErrorActionPreference = "Stop"
$params = Parse-Args -arguments $args -supports_check_mode $true
$path = Get-AnsibleParam -obj $params -name "path" -type "str" -failifempty $true -aliases "key"
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -aliases "entry","value"
$result = @{
changed = $false
}
Function Get-PropertyValue {
param(
[Parameter(Mandatory=$true)][Microsoft.Win32.RegistryKey]$Key,
[String]$Name
)
$value = $Key.GetValue($Name, $null, [Microsoft.Win32.RegistryValueOptions]::None)
if ($null -eq $value) {
# Property does not exist or the key's (Default) is not set
return $null
}
$raw_value = $Key.GetValue($Name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($Name -eq "") {
# The key's (Default) will fail on GetValueKind
$type = [Microsoft.Win32.RegistryValueKind]::String
} else {
$type = $Key.GetValueKind($Name)
}
if ($type -in @([Microsoft.Win32.RegistryValueKind]::Binary, [Microsoft.Win32.RegistryValueKind]::None)) {
$formatted_raw_value = [System.Collections.Generic.List`1[String]]@()
foreach ($byte in $value) {
$formatted_raw_value.Add("0x{0:x2}" -f $byte)
}
$raw_value = $formatted_raw_value
} elseif ($type -eq [Microsoft.Win32.RegistryValueKind]::DWord) {
# .NET returns the value as a signed integer, we need to make it unsigned
$value = [UInt32]("0x{0:x}" -f $value)
$raw_value = $value
} elseif ($type -eq [Microsoft.Win32.RegistryValueKind]::QWord) {
$value = [UInt64]("0x{0:x}" -f $value)
$raw_value = $value
}
$return_type = switch($type.ToString()) {
"Binary" { "REG_BINARY" }
"String" { "REG_SZ" }
"DWord" { "REG_DWORD" }
"QWord" { "REG_QWORD" }
"MultiString" { "REG_MULTI_SZ" }
"ExpandString" { "REG_EXPAND_SZ" }
"None" { "REG_NONE" }
default { "Unknown - $($type.ToString())" }
}
return @{
type = $return_type
value = $value
raw_value = $raw_value
}
}
# Will validate the key parameter to make sure it matches known format
if ($path -notmatch "^HK(CC|CR|CU|LM|U):\\") {
Fail-Json -obj $result -message "path: $path is not a valid registry path, see module documentation for examples."
}
$registry_path = (Split-Path -Path $path -NoQualifier).Substring(1) # removes the hive: and leading \
$registry_hive = switch(Split-Path -Path $path -Qualifier) {
"HKCR:" { [Microsoft.Win32.Registry]::ClassesRoot }
"HKCC:" { [Microsoft.Win32.Registry]::CurrentConfig }
"HKCU:" { [Microsoft.Win32.Registry]::CurrentUser }
"HKLM:" { [Microsoft.Win32.Registry]::LocalMachine }
"HKU:" { [Microsoft.Win32.Registry]::Users }
}
$key = $null
try {
$key = $registry_hive.OpenSubKey($registry_path, $false)
if ($null -ne $key) {
if ($null -eq $name) {
$property_info = @{}
foreach ($property in $key.GetValueNames()) {
$property_info.$property = Get-PropertyValue -Key $key -Name $property
}
# Return the key's (Default) property if it has been defined
$default_value = Get-PropertyValue -Key $key -Name ""
if ($null -ne $default_value) {
$property_info."" = $default_value
}
$result.exists = $true
$result.properties = $property_info
$result.sub_keys = $key.GetSubKeyNames()
} else {
$property_value = Get-PropertyValue -Key $key -Name $name
if ($null -ne $property_value) {
$result.exists = $true
$result += $property_value
} else {
$result.exists = $false
}
}
} else {
$result.exists = $false
}
} finally {
if ($key) {
$key.Dispose()
}
$registry_hive.Dispose()
}
Exit-Json -obj $result

@ -1,121 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2016, Ansible, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_reg_stat
version_added: "2.3"
short_description: Get information about Windows registry keys
description:
- Like M(win_file), M(win_reg_stat) will return whether the key/property exists.
- It also returns the sub keys and properties of the key specified.
- If specifying a property name through I(property), it will return the information specific for that property.
options:
path:
description: The full registry key path including the hive to search for.
type: str
required: yes
aliases: [ key ]
name:
description:
- The registry property name to get information for, the return json will not include the sub_keys and properties entries for the I(key) specified.
- Set to an empty string to target the registry key's C((Default)) property value.
type: str
aliases: [ entry, value, property ]
notes:
- The C(properties) return value will contain an empty string key C("") that refers to the key's C(Default) value. If
the value has not been set then this key is not returned.
seealso:
- module: win_regedit
- module: win_regmerge
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Obtain information about a registry key using short form
win_reg_stat:
path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion
register: current_version
- name: Obtain information about a registry key property
win_reg_stat:
path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion
name: CommonFilesDir
register: common_files_dir
- name: Obtain the registry key's (Default) property
win_reg_stat:
path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion
name: ''
register: current_version_default
'''
RETURN = r'''
changed:
description: Whether anything was changed.
returned: always
type: bool
sample: true
exists:
description: States whether the registry key/property exists.
returned: success and path/property exists
type: bool
sample: true
properties:
description: A dictionary containing all the properties and their values in the registry key.
returned: success, path exists and property not specified
type: dict
sample: {
"" : {
"raw_value": "",
"type": "REG_SZ",
"value": ""
},
"binary_property" : {
"raw_value": ["0x01", "0x16"],
"type": "REG_BINARY",
"value": [1, 22]
},
"multi_string_property" : {
"raw_value": ["a", "b"],
"type": "REG_MULTI_SZ",
"value": ["a", "b"]
}
}
sub_keys:
description: A list of all the sub keys of the key specified.
returned: success, path exists and property not specified
type: list
sample: [
"AppHost",
"Casting",
"DateTime"
]
raw_value:
description: Returns the raw value of the registry property, REG_EXPAND_SZ has no string expansion, REG_BINARY or REG_NONE is in hex 0x format.
REG_NONE, this value is a hex string in the 0x format.
returned: success, path/property exists and property specified
type: str
sample: '%ProgramDir%\\Common Files'
type:
description: The property type.
returned: success, path/property exists and property specified
type: str
sample: "REG_EXPAND_SZ"
value:
description: The value of the property.
returned: success, path/property exists and property specified
type: str
sample: 'C:\\Program Files\\Common Files'
'''

@ -1,495 +0,0 @@
#!powershell
# Copyright: (c) 2015, Adam Keech <akeech@chathamfinancial.com>
# Copyright: (c) 2015, Josh Ludwig <jludwig@chathamfinancial.com>
# Copyright: (c) 2017, Jordan Borean <jborean93@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.PrivilegeUtil
$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
$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP
$path = Get-AnsibleParam -obj $params -name "path" -type "str" -failifempty $true -aliases "key"
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -aliases "entry","value"
$data = Get-AnsibleParam -obj $params -name "data"
$type = Get-AnsibleParam -obj $params -name "type" -type "str" -default "string" -validateset "none","binary","dword","expandstring","multistring","string","qword" -aliases "datatype"
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent"
$delete_key = Get-AnsibleParam -obj $params -name "delete_key" -type "bool" -default $true
$hive = Get-AnsibleParam -obj $params -name "hive" -type "path"
$result = @{
changed = $false
data_changed = $false
data_type_changed = $false
}
if ($diff_mode) {
$result.diff = @{
before = ""
after = ""
}
}
$registry_util = @'
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Ansible.WinRegedit
{
internal class NativeMethods
{
[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
public static extern int RegLoadKeyW(
UInt32 hKey,
string lpSubKey,
string lpFile);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
public static extern int RegUnLoadKeyW(
UInt32 hKey,
string lpSubKey);
}
public class Win32Exception : System.ComponentModel.Win32Exception
{
private string _msg;
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
public Win32Exception(int errorCode, string message) : base(errorCode)
{
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
}
public override string Message { get { return _msg; } }
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
}
public class Hive : IDisposable
{
private const UInt32 SCOPE = 0x80000002; // HKLM
private string hiveKey;
private bool loaded = false;
public Hive(string hiveKey, string hivePath)
{
this.hiveKey = hiveKey;
int ret = NativeMethods.RegLoadKeyW(SCOPE, hiveKey, hivePath);
if (ret != 0)
throw new Win32Exception(ret, String.Format("Failed to load registry hive at {0}", hivePath));
loaded = true;
}
public static void UnloadHive(string hiveKey)
{
int ret = NativeMethods.RegUnLoadKeyW(SCOPE, hiveKey);
if (ret != 0)
throw new Win32Exception(ret, String.Format("Failed to unload registry hive at {0}", hiveKey));
}
public void Dispose()
{
if (loaded)
{
// Make sure the garbage collector disposes all unused handles and waits until it is complete
GC.Collect();
GC.WaitForPendingFinalizers();
UnloadHive(hiveKey);
loaded = false;
}
GC.SuppressFinalize(this);
}
~Hive() { this.Dispose(); }
}
}
'@
# fire a warning if the property name isn't specified, the (Default) key ($null) can only be a string
if ($null -eq $name -and $type -ne "string") {
Add-Warning -obj $result -message "the data type when name is not specified can only be 'string', the type has automatically been converted"
$type = "string"
}
# Check that the registry path is in PSDrive format: HKCC, HKCR, HKCU, HKLM, HKU
if ($path -notmatch "^HK(CC|CR|CU|LM|U):\\") {
Fail-Json $result "path: $path is not a valid powershell path, see module documentation for examples."
}
# Add a warning if the path does not contains a \ and is not the leaf path
$registry_path = (Split-Path -Path $path -NoQualifier).Substring(1) # removes the hive: and leading \
$registry_leaf = Split-Path -Path $path -Leaf
if ($registry_path -ne $registry_leaf -and -not $registry_path.Contains('\')) {
$msg = "path is not using '\' as a separator, support for '/' as a separator will be removed in a future Ansible version"
Add-DeprecationWarning -obj $result -message $msg -version 2.12
$registry_path = $registry_path.Replace('/', '\')
}
# Simplified version of Convert-HexStringToByteArray from
# https://cyber-defense.sans.org/blog/2010/02/11/powershell-byte-array-hex-convert
# Expects a hex in the format you get when you run reg.exe export,
# and converts to a byte array so powershell can modify binary registry entries
# import format is like 'hex:be,ef,be,ef,be,ef,be,ef,be,ef'
Function Convert-RegExportHexStringToByteArray($string) {
# Remove 'hex:' from the front of the string if present
$string = $string.ToLower() -replace '^hex\:',''
# Remove whitespace and any other non-hex crud.
$string = $string -replace '[^a-f0-9\\,x\-\:]',''
# Turn commas into colons
$string = $string -replace ',',':'
# Maybe there's nothing left over to convert...
if ($string.Length -eq 0) {
return ,@()
}
# Split string with or without colon delimiters.
if ($string.Length -eq 1) {
return ,@([System.Convert]::ToByte($string,16))
} elseif (($string.Length % 2 -eq 0) -and ($string.IndexOf(":") -eq -1)) {
return ,@($string -split '([a-f0-9]{2})' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}})
} elseif ($string.IndexOf(":") -ne -1) {
return ,@($string -split ':+' | foreach-object {[System.Convert]::ToByte($_,16)})
} else {
return ,@()
}
}
Function Compare-RegistryProperties($existing, $new) {
# Outputs $true if the property values don't match
if ($existing -is [Array]) {
(Compare-Object -ReferenceObject $existing -DifferenceObject $new -SyncWindow 0).Length -ne 0
} else {
$existing -cne $new
}
}
Function Get-DiffValue {
param(
[Parameter(Mandatory=$true)][Microsoft.Win32.RegistryValueKind]$Type,
[Parameter(Mandatory=$true)][Object]$Value
)
$diff = @{ type = $Type.ToString(); value = $Value }
$enum = [Microsoft.Win32.RegistryValueKind]
if ($Type -in @($enum::Binary, $enum::None)) {
$diff.value = [System.Collections.Generic.List`1[String]]@()
foreach ($dec_value in $Value) {
$diff.value.Add("0x{0:x2}" -f $dec_value)
}
} elseif ($Type -eq $enum::DWord) {
$diff.value = "0x{0:x8}" -f $Value
} elseif ($Type -eq $enum::QWord) {
$diff.value = "0x{0:x16}" -f $Value
}
return $diff
}
Function Set-StateAbsent {
param(
# Used for diffs and exception messages to match up against Ansible input
[Parameter(Mandatory=$true)][String]$PrintPath,
[Parameter(Mandatory=$true)][Microsoft.Win32.RegistryKey]$Hive,
[Parameter(Mandatory=$true)][String]$Path,
[String]$Name,
[Switch]$DeleteKey
)
$key = $Hive.OpenSubKey($Path, $true)
if ($null -eq $key) {
# Key does not exist, no need to delete anything
return
}
try {
if ($DeleteKey -and -not $Name) {
# delete_key=yes is set and name is null/empty, so delete the entire key
$key.Dispose()
$key = $null
if (-not $check_mode) {
try {
$Hive.DeleteSubKeyTree($Path, $false)
} catch {
Fail-Json -obj $result -message "failed to delete registry key at $($PrintPath): $($_.Exception.Message)"
}
}
$result.changed = $true
if ($diff_mode) {
$result.diff.before = @{$PrintPath = @{}}
$result.diff.after = @{}
}
} else {
# delete_key=no or name is not null/empty, delete the property not the full key
$property = $key.GetValue($Name)
if ($null -eq $property) {
# property does not exist
return
}
$property_type = $key.GetValueKind($Name) # used for the diff
if (-not $check_mode) {
try {
$key.DeleteValue($Name)
} catch {
Fail-Json -obj $result -message "failed to delete registry property '$Name' at $($PrintPath): $($_.Exception.Message)"
}
}
$result.changed = $true
if ($diff_mode) {
$diff_value = Get-DiffValue -Type $property_type -Value $property
$result.diff.before = @{ $PrintPath = @{ $Name = $diff_value } }
$result.diff.after = @{ $PrintPath = @{} }
}
}
} finally {
if ($key) {
$key.Dispose()
}
}
}
Function Set-StatePresent {
param(
[Parameter(Mandatory=$true)][String]$PrintPath,
[Parameter(Mandatory=$true)][Microsoft.Win32.RegistryKey]$Hive,
[Parameter(Mandatory=$true)][String]$Path,
[String]$Name,
[Object]$Data,
[Microsoft.Win32.RegistryValueKind]$Type
)
$key = $Hive.OpenSubKey($Path, $true)
try {
if ($null -eq $key) {
# the key does not exist, create it so the next steps work
if (-not $check_mode) {
try {
$key = $Hive.CreateSubKey($Path)
} catch {
Fail-Json -obj $result -message "failed to create registry key at $($PrintPath): $($_.Exception.Message)"
}
}
$result.changed = $true
if ($diff_mode) {
$result.diff.before = @{}
$result.diff.after = @{$PrintPath = @{}}
}
} elseif ($diff_mode) {
# Make sure the diff is in an expected state for the key
$result.diff.before = @{$PrintPath = @{}}
$result.diff.after = @{$PrintPath = @{}}
}
if ($null -eq $key -or $null -eq $Data) {
# Check mode and key was created above, we cannot do any more work, or $Data is $null which happens when
# we create a new key but haven't explicitly set the data
return
}
$property = $key.GetValue($Name, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($null -ne $property) {
# property exists, need to compare the values and type
$existing_type = $key.GetValueKind($name)
$change_value = $false
if ($Type -ne $existing_type) {
$change_value = $true
$result.data_type_changed = $true
$data_mismatch = Compare-RegistryProperties -existing $property -new $Data
if ($data_mismatch) {
$result.data_changed = $true
}
} else {
$data_mismatch = Compare-RegistryProperties -existing $property -new $Data
if ($data_mismatch) {
$change_value = $true
$result.data_changed = $true
}
}
if ($change_value) {
if (-not $check_mode) {
try {
$key.SetValue($Name, $Data, $Type)
} catch {
Fail-Json -obj $result -message "failed to change registry property '$Name' at $($PrintPath): $($_.Exception.Message)"
}
}
$result.changed = $true
if ($diff_mode) {
$result.diff.before.$PrintPath.$Name = Get-DiffValue -Type $existing_type -Value $property
$result.diff.after.$PrintPath.$Name = Get-DiffValue -Type $Type -Value $Data
}
} elseif ($diff_mode) {
$diff_value = Get-DiffValue -Type $existing_type -Value $property
$result.diff.before.$PrintPath.$Name = $diff_value
$result.diff.after.$PrintPath.$Name = $diff_value
}
} else {
# property doesn't exist just create a new one
if (-not $check_mode) {
try {
$key.SetValue($Name, $Data, $Type)
} catch {
Fail-Json -obj $result -message "failed to create registry property '$Name' at $($PrintPath): $($_.Exception.Message)"
}
}
$result.changed = $true
if ($diff_mode) {
$result.diff.after.$PrintPath.$Name = Get-DiffValue -Type $Type -Value $Data
}
}
} finally {
if ($key) {
$key.Dispose()
}
}
}
# convert property names "" to $null as "" refers to (Default)
if ($name -eq "") {
$name = $null
}
# convert the data to the required format
if ($type -in @("binary", "none")) {
if ($null -eq $data) {
$data = ""
}
# convert the data from string to byte array if in hex: format
if ($data -is [String]) {
$data = [byte[]](Convert-RegExportHexStringToByteArray -string $data)
} elseif ($data -is [Int]) {
if ($data -gt 255) {
Fail-Json $result "cannot convert binary data '$data' to byte array, please specify this value as a yaml byte array or a comma separated hex value string"
}
$data = [byte[]]@([byte]$data)
} elseif ($data -is [Array]) {
$data = [byte[]]$data
}
} elseif ($type -in @("dword", "qword")) {
# dword's and dword's don't allow null values, set to 0
if ($null -eq $data) {
$data = 0
}
if ($data -is [String]) {
# if the data is a string we need to convert it to an unsigned int64
# it needs to be unsigned as Ansible passes in an unsigned value while
# powershell uses a signed data type. The value will then be converted
# below
$data = [UInt64]$data
}
if ($type -eq "dword") {
if ($data -gt [UInt32]::MaxValue) {
Fail-Json $result "data cannot be larger than 0xffffffff when type is dword"
} elseif ($data -gt [Int32]::MaxValue) {
# when dealing with larger int32 (> 2147483647 or 0x7FFFFFFF) powershell
# automatically converts it to a signed int64. We need to convert this to
# signed int32 by parsing the hex string value.
$data = "0x$("{0:x}" -f $data)"
}
$data = [Int32]$data
} else {
if ($data -gt [UInt64]::MaxValue) {
Fail-Json $result "data cannot be larger than 0xffffffffffffffff when type is qword"
} elseif ($data -gt [Int64]::MaxValue) {
$data = "0x$("{0:x}" -f $data)"
}
$data = [Int64]$data
}
} elseif ($type -in @("string", "expandstring") -and $name) {
# a null string or expandstring must be empty quotes
# Only do this if $name has been defined (not the default key)
if ($null -eq $data) {
$data = ""
}
} elseif ($type -eq "multistring") {
# convert the data for a multistring to a String[] array
if ($null -eq $data) {
$data = [String[]]@()
} elseif ($data -isnot [Array]) {
$new_data = New-Object -TypeName String[] -ArgumentList 1
$new_data[0] = $data.ToString([CultureInfo]::InvariantCulture)
$data = $new_data
} else {
$new_data = New-Object -TypeName String[] -ArgumentList $data.Count
foreach ($entry in $data) {
$new_data[$data.IndexOf($entry)] = $entry.ToString([CultureInfo]::InvariantCulture)
}
$data = $new_data
}
}
# convert the type string to the .NET class
$type = [System.Enum]::Parse([Microsoft.Win32.RegistryValueKind], $type, $true)
$registry_hive = switch(Split-Path -Path $path -Qualifier) {
"HKCR:" { [Microsoft.Win32.Registry]::ClassesRoot }
"HKCC:" { [Microsoft.Win32.Registry]::CurrentConfig }
"HKCU:" { [Microsoft.Win32.Registry]::CurrentUser }
"HKLM:" { [Microsoft.Win32.Registry]::LocalMachine }
"HKU:" { [Microsoft.Win32.Registry]::Users }
}
$loaded_hive = $null
try {
if ($hive) {
if (-not (Test-Path -LiteralPath $hive)) {
Fail-Json -obj $result -message "hive at path '$hive' is not valid or accessible, cannot load hive"
}
$original_tmp = $env:TMP
$env:TMP = $_remote_tmp
Add-Type -TypeDefinition $registry_util
$env:TMP = $original_tmp
try {
Set-AnsiblePrivilege -Name SeBackupPrivilege -Value $true
Set-AnsiblePrivilege -Name SeRestorePrivilege -Value $true
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to enable SeBackupPrivilege and SeRestorePrivilege for the current process: $($_.Exception.Message)"
}
if (Test-Path -Path HKLM:\ANSIBLE) {
Add-Warning -obj $result -message "hive already loaded at HKLM:\ANSIBLE, had to unload hive for win_regedit to continue"
try {
[Ansible.WinRegedit.Hive]::UnloadHive("ANSIBLE")
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to unload registry hive HKLM:\ANSIBLE from $($hive): $($_.Exception.Message)"
}
}
try {
$loaded_hive = New-Object -TypeName Ansible.WinRegedit.Hive -ArgumentList "ANSIBLE", $hive
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "failed to load registry hive from '$hive' to HKLM:\ANSIBLE: $($_.Exception.Message)"
}
}
if ($state -eq "present") {
Set-StatePresent -PrintPath $path -Hive $registry_hive -Path $registry_path -Name $name -Data $data -Type $type
} else {
Set-StateAbsent -PrintPath $path -Hive $registry_hive -Path $registry_path -Name $name -DeleteKey:$delete_key
}
} finally {
$registry_hive.Dispose()
if ($loaded_hive) {
$loaded_hive.Dispose()
}
}
Exit-Json $result

@ -1,210 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Adam Keech <akeech@chathamfinancial.com>
# Copyright: (c) 2015, Josh Ludwig <jludwig@chathamfinancial.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_regedit
version_added: '2.0'
short_description: Add, change, or remove registry keys and values
description:
- Add, modify or remove registry keys and values.
- More information about the windows registry from Wikipedia
U(https://en.wikipedia.org/wiki/Windows_Registry).
options:
path:
description:
- Name of the registry path.
- 'Should be in one of the following registry hives: HKCC, HKCR, HKCU,
HKLM, HKU.'
type: str
required: yes
aliases: [ key ]
name:
description:
- Name of the registry entry in the above C(path) parameters.
- If not provided, or empty then the '(Default)' property for the key will
be used.
type: str
aliases: [ entry, value ]
data:
description:
- Value of the registry entry C(name) in C(path).
- If not specified then the value for the property will be null for the
corresponding C(type).
- Binary and None data should be expressed in a yaml byte array or as comma
separated hex values.
- An easy way to generate this is to run C(regedit.exe) and use the
I(export) option to save the registry values to a file.
- In the exported file, binary value will look like C(hex:be,ef,be,ef), the
C(hex:) prefix is optional.
- DWORD and QWORD values should either be represented as a decimal number
or a hex value.
- Multistring values should be passed in as a list.
- See the examples for more details on how to format this data.
type: str
type:
description:
- The registry value data type.
type: str
choices: [ binary, dword, expandstring, multistring, string, qword ]
default: string
aliases: [ datatype ]
state:
description:
- The state of the registry entry.
type: str
choices: [ absent, present ]
default: present
delete_key:
description:
- When C(state) is 'absent' then this will delete the entire key.
- If C(no) then it will only clear out the '(Default)' property for
that key.
type: bool
default: yes
version_added: '2.4'
hive:
description:
- A path to a hive key like C:\Users\Default\NTUSER.DAT to load in the
registry.
- This hive is loaded under the HKLM:\ANSIBLE key which can then be used
in I(name) like any other path.
- This can be used to load the default user profile registry hive or any
other hive saved as a file.
- Using this function requires the user to have the C(SeRestorePrivilege)
and C(SeBackupPrivilege) privileges enabled.
type: path
version_added: '2.5'
notes:
- Check-mode C(-C/--check) and diff output C(-D/--diff) are supported, so that you can test every change against the active configuration before
applying changes.
- Beware that some registry hives (C(HKEY_USERS) in particular) do not allow to create new registry paths in the root folder.
- Since ansible 2.4, when checking if a string registry value has changed, a case-sensitive test is used. Previously the test was case-insensitive.
seealso:
- module: win_reg_stat
- module: win_regmerge
author:
- Adam Keech (@smadam813)
- Josh Ludwig (@joshludwig)
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Create registry path MyCompany
win_regedit:
path: HKCU:\Software\MyCompany
- name: Add or update registry path MyCompany, with entry 'hello', and containing 'world'
win_regedit:
path: HKCU:\Software\MyCompany
name: hello
data: world
- name: Add or update registry path MyCompany, with dword entry 'hello', and containing 1337 as the decimal value
win_regedit:
path: HKCU:\Software\MyCompany
name: hello
data: 1337
type: dword
- name: Add or update registry path MyCompany, with dword entry 'hello', and containing 0xff2500ae as the hex value
win_regedit:
path: HKCU:\Software\MyCompany
name: hello
data: 0xff2500ae
type: dword
- name: Add or update registry path MyCompany, with binary entry 'hello', and containing binary data in hex-string format
win_regedit:
path: HKCU:\Software\MyCompany
name: hello
data: hex:be,ef,be,ef,be,ef,be,ef,be,ef
type: binary
- name: Add or update registry path MyCompany, with binary entry 'hello', and containing binary data in yaml format
win_regedit:
path: HKCU:\Software\MyCompany
name: hello
data: [0xbe,0xef,0xbe,0xef,0xbe,0xef,0xbe,0xef,0xbe,0xef]
type: binary
- name: Add or update registry path MyCompany, with expand string entry 'hello'
win_regedit:
path: HKCU:\Software\MyCompany
name: hello
data: '%appdata%\local'
type: expandstring
- name: Add or update registry path MyCompany, with multi string entry 'hello'
win_regedit:
path: HKCU:\Software\MyCompany
name: hello
data: ['hello', 'world']
type: multistring
- name: Disable keyboard layout hotkey for all users (changes existing)
win_regedit:
path: HKU:\.DEFAULT\Keyboard Layout\Toggle
name: Layout Hotkey
data: 3
type: dword
- name: Disable language hotkey for current users (adds new)
win_regedit:
path: HKCU:\Keyboard Layout\Toggle
name: Language Hotkey
data: 3
type: dword
- name: Remove registry path MyCompany (including all entries it contains)
win_regedit:
path: HKCU:\Software\MyCompany
state: absent
delete_key: yes
- name: Clear the existing (Default) entry at path MyCompany
win_regedit:
path: HKCU:\Software\MyCompany
state: absent
delete_key: no
- name: Remove entry 'hello' from registry path MyCompany
win_regedit:
path: HKCU:\Software\MyCompany
name: hello
state: absent
- name: Change default mouse trailing settings for new users
win_regedit:
path: HKLM:\ANSIBLE\Control Panel\Mouse
name: MouseTrails
data: 10
type: str
state: present
hive: C:\Users\Default\NTUSER.dat
'''
RETURN = r'''
data_changed:
description: Whether this invocation changed the data in the registry value.
returned: success
type: bool
sample: false
data_type_changed:
description: Whether this invocation changed the datatype of the registry value.
returned: success
type: bool
sample: true
'''

@ -1,463 +0,0 @@
#!powershell
# Copyright: (c) 2014, Chris Hoffman <choffman@chathamfinancial.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.SID
$ErrorActionPreference = "Stop"
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name '_ansible_check_mode' -type 'bool' -default $false
$dependencies = Get-AnsibleParam -obj $params -name 'dependencies' -type 'list' -default $null
$dependency_action = Get-AnsibleParam -obj $params -name 'dependency_action' -type 'str' -default 'set' -validateset 'add','remove','set'
$description = Get-AnsibleParam -obj $params -name 'description' -type 'str'
$desktop_interact = Get-AnsibleParam -obj $params -name 'desktop_interact' -type 'bool' -default $false
$display_name = Get-AnsibleParam -obj $params -name 'display_name' -type 'str'
$force_dependent_services = Get-AnsibleParam -obj $params -name 'force_dependent_services' -type 'bool' -default $false
$name = Get-AnsibleParam -obj $params -name 'name' -type 'str' -failifempty $true
$password = Get-AnsibleParam -obj $params -name 'password' -type 'str'
$path = Get-AnsibleParam -obj $params -name 'path'
$start_mode = Get-AnsibleParam -obj $params -name 'start_mode' -type 'str' -validateset 'auto','manual','disabled','delayed'
$state = Get-AnsibleParam -obj $params -name 'state' -type 'str' -validateset 'started','stopped','restarted','absent','paused'
$username = Get-AnsibleParam -obj $params -name 'username' -type 'str'
$result = @{
changed = $false
}
# parse the username to SID and back so we get the full username with domain in a way WMI understands
if ($null -ne $username) {
if ($username -eq "LocalSystem") {
$username_sid = "S-1-5-18"
} else {
$username_sid = Convert-ToSID -account_name $username
}
# the SYSTEM account is a special beast, Win32_Service Change requires StartName to be LocalSystem
# to specify LocalSystem/NT AUTHORITY\SYSTEM
if ($username_sid -eq "S-1-5-18") {
$username = "LocalSystem"
$password = $null
} else {
# Win32_Service, password must be "" and not $null when setting to LocalService or NetworkService
if ($username_sid -in @("S-1-5-19", "S-1-5-20")) {
$password = ""
}
$username = Convert-FromSID -sid $username_sid
}
}
if ($null -ne $password -and $null -eq $username) {
Fail-Json $result "The argument 'username' must be supplied with 'password'"
}
if ($desktop_interact -eq $true -and (-not ($username -eq "LocalSystem" -or $null -eq $username))) {
Fail-Json $result "Can only set 'desktop_interact' to true when 'username' equals 'LocalSystem'"
}
if ($null -ne $path) {
$path = [System.Environment]::ExpandEnvironmentVariables($path)
}
Function Get-ServiceInfo($name) {
# Need to get new objects so we have the latest info
$svc = Get-Service | Where-Object { $_.Name -eq $name -or $_.DisplayName -eq $name }
$wmi_svc = Get-CimInstance -ClassName Win32_Service -Filter "name='$($svc.Name)'"
# Delayed start_mode is in reality Automatic (Delayed), need to check reg key for type
$delayed = Get-DelayedStatus -name $svc.Name
$actual_start_mode = $wmi_svc.StartMode.ToString().ToLower()
if ($delayed -and $actual_start_mode -eq 'auto') {
$actual_start_mode = 'delayed'
}
$existing_dependencies = @()
$existing_depended_by = @()
if ($svc.ServicesDependedOn.Count -gt 0) {
foreach ($dependency in $svc.ServicesDependedOn.Name) {
$existing_dependencies += $dependency
}
}
if ($svc.DependentServices.Count -gt 0) {
foreach ($dependency in $svc.DependentServices.Name) {
$existing_depended_by += $dependency
}
}
$description = $wmi_svc.Description
if ($null -eq $description) {
$description = ""
}
$result.exists = $true
$result.name = $svc.Name
$result.display_name = $svc.DisplayName
$result.state = $svc.Status.ToString().ToLower()
$result.start_mode = $actual_start_mode
$result.path = $wmi_svc.PathName
$result.description = $description
$result.username = $wmi_svc.StartName
$result.desktop_interact = $wmi_svc.DesktopInteract
$result.dependencies = $existing_dependencies
$result.depended_by = $existing_depended_by
$result.can_pause_and_continue = $svc.CanPauseAndContinue
}
Function Get-WmiErrorMessage($return_value) {
# These values are derived from https://msdn.microsoft.com/en-us/library/aa384901(v=vs.85).aspx
switch ($return_value) {
1 { "Not Supported: The request is not supported" }
2 { "Access Denied: The user did not have the necessary access" }
3 { "Dependent Services Running: The service cannot be stopped because other services that are running are dependent on it" }
4 { "Invalid Service Control: The requested control code is not valid, or it is unacceptable to the service" }
5 { "Service Cannot Accept Control: The requested control code cannot be sent to the service because the state of the service (Win32_BaseService.State property) is equal to 0, 1, or 2" }
6 { "Service Not Active: The service has not been started" }
7 { "Service Request Timeout: The service did not respond to the start request in a timely fashion" }
8 { "Unknown Failure: Unknown failure when starting the service" }
9 { "Path Not Found: The directory path to the service executable file was not found" }
10 { "Service Already Running: The service is already running" }
11 { "Service Database Locked: The database to add a new service is locked" }
12 { "Service Dependency Deleted: A dependency this service relies on has been removed from the system" }
13 { "Service Dependency Failure: The service failed to find the service needed from a dependent service" }
14 { "Service Disabled: The service has been disabled from the system" }
15 { "Service Logon Failed: The service does not have the correct authentication to run on the system" }
16 { "Service Marked For Deletion: This service is being removed from the system" }
17 { "Service No Thread: The service has no execution thread" }
18 { "Status Circular Dependency: The service has circular dependencies when it starts" }
19 { "Status Duplicate Name: A service is running under the same name" }
20 { "Status Invalid Name: The service name has invalid characters" }
21 { "Status Invalid Parameter: Invalid parameters have been passed to the service" }
22 { "Status Invalid Service Account: The account under which this service runs is either invalid or lacks the permissions to run the service" }
23 { "Status Service Exists: The service exists in the database of services available from the system" }
24 { "Service Already Paused: The service is currently paused in the system" }
default { "Other Error" }
}
}
Function Get-DelayedStatus($name) {
$delayed_key = "HKLM:\System\CurrentControlSet\Services\$name"
try {
$delayed = ConvertTo-Bool ((Get-ItemProperty -LiteralPath $delayed_key).DelayedAutostart)
} catch {
$delayed = $false
}
$delayed
}
Function Set-ServiceStartMode($svc, $start_mode) {
if ($result.start_mode -ne $start_mode) {
try {
$delayed_key = "HKLM:\System\CurrentControlSet\Services\$($svc.Name)"
# Original start up type was auto (delayed) and we want auto, need to removed delayed key
if ($start_mode -eq 'auto' -and $result.start_mode -eq 'delayed') {
Set-ItemProperty -LiteralPath $delayed_key -Name "DelayedAutostart" -Value 0 -WhatIf:$check_mode
# Original start up type was auto and we want auto (delayed), need to add delayed key
} elseif ($start_mode -eq 'delayed' -and $result.start_mode -eq 'auto') {
Set-ItemProperty -LiteralPath $delayed_key -Name "DelayedAutostart" -Value 1 -WhatIf:$check_mode
# Original start up type was not auto or auto (delayed), need to change to auto and add delayed key
} elseif ($start_mode -eq 'delayed') {
$svc | Set-Service -StartupType "auto" -WhatIf:$check_mode
Set-ItemProperty -LiteralPath $delayed_key -Name "DelayedAutostart" -Value 1 -WhatIf:$check_mode
# Original start up type was not what we were looking for, just change to that type
} else {
$svc | Set-Service -StartupType $start_mode -WhatIf:$check_mode
}
} catch {
Fail-Json $result $_.Exception.Message
}
$result.changed = $true
}
}
Function Set-ServiceAccount($wmi_svc, $username_sid, $username, $password) {
if ($result.username -eq "LocalSystem") {
$actual_sid = "S-1-5-18"
} else {
$actual_sid = Convert-ToSID -account_name $result.username
}
if ($actual_sid -ne $username_sid) {
$change_arguments = @{
StartName = $username
StartPassword = $password
DesktopInteract = $result.desktop_interact
}
# need to disable desktop interact when not using the SYSTEM account
if ($username_sid -ne "S-1-5-18") {
$change_arguments.DesktopInteract = $false
}
#WMI.Change doesn't support -WhatIf, cannot fully test with check_mode
if (-not $check_mode) {
$return = $wmi_svc | Invoke-CimMethod -MethodName Change -Arguments $change_arguments
if ($return.ReturnValue -ne 0) {
$error_msg = Get-WmiErrorMessage -return_value $result.ReturnValue
Fail-Json -obj $result -message "Failed to set service account to $($username): $($return.ReturnValue) - $error_msg"
}
}
$result.changed = $true
}
}
Function Set-ServiceDesktopInteract($wmi_svc, $desktop_interact) {
if ($result.desktop_interact -ne $desktop_interact) {
if (-not $check_mode) {
$return = $wmi_svc | Invoke-CimMethod -MethodName Change -Arguments @{DesktopInteract = $desktop_interact}
if ($return.ReturnValue -ne 0) {
$error_msg = Get-WmiErrorMessage -return_value $return.ReturnValue
Fail-Json -obj $result -message "Failed to set desktop interact $($desktop_interact): $($return.ReturnValue) - $error_msg"
}
}
$result.changed = $true
}
}
Function Set-ServiceDisplayName($svc, $display_name) {
if ($result.display_name -ne $display_name) {
try {
$svc | Set-Service -DisplayName $display_name -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
$result.changed = $true
}
}
Function Set-ServiceDescription($svc, $description) {
if ($result.description -ne $description) {
try {
$svc | Set-Service -Description $description -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
$result.changed = $true
}
}
Function Set-ServicePath($name, $path) {
if ($result.path -ne $path) {
try {
Set-ItemProperty -LiteralPath "HKLM:\System\CurrentControlSet\Services\$name" -Name ImagePath -Value $path -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
$result.changed = $true
}
}
Function Set-ServiceDependencies($wmi_svc, $dependency_action, $dependencies) {
$existing_dependencies = $result.dependencies
[System.Collections.ArrayList]$new_dependencies = @()
if ($dependency_action -eq 'set') {
foreach ($dependency in $dependencies) {
$new_dependencies.Add($dependency)
}
} else {
$new_dependencies = $existing_dependencies
foreach ($dependency in $dependencies) {
if ($dependency_action -eq 'remove') {
if ($new_dependencies -contains $dependency) {
$new_dependencies.Remove($dependency)
}
} elseif ($dependency_action -eq 'add') {
if ($new_dependencies -notcontains $dependency) {
$new_dependencies.Add($dependency)
}
}
}
}
$will_change = $false
foreach ($dependency in $new_dependencies) {
if ($existing_dependencies -notcontains $dependency) {
$will_change = $true
}
}
foreach ($dependency in $existing_dependencies) {
if ($new_dependencies -notcontains $dependency) {
$will_change = $true
}
}
if ($will_change -eq $true) {
if (-not $check_mode) {
$return = $wmi_svc | Invoke-CimMethod -MethodName Change -Arguments @{ServiceDependencies = $new_dependencies}
if ($return.ReturnValue -ne 0) {
$error_msg = Get-WmiErrorMessage -return_value $return.ReturnValue
$dep_string = $new_dependencies -join ", "
Fail-Json -obj $result -message "Failed to set service dependencies $($dep_string): $($return.ReturnValue) - $error_msg"
}
}
$result.changed = $true
}
}
Function Set-ServiceState($svc, $wmi_svc, $state) {
if ($state -eq "started" -and $result.state -ne "running") {
if ($result.state -eq "paused") {
try {
$svc | Resume-Service -WhatIf:$check_mode
} catch {
Fail-Json $result "failed to start service from paused state $($svc.Name): $($_.Exception.Message)"
}
} else {
try {
$svc | Start-Service -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
}
$result.changed = $true
}
if ($state -eq "stopped" -and $result.state -ne "stopped") {
try {
$svc | Stop-Service -Force:$force_dependent_services -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
$result.changed = $true
}
if ($state -eq "restarted") {
try {
$svc | Restart-Service -Force:$force_dependent_services -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
$result.changed = $true
}
if ($state -eq "paused" -and $result.state -ne "paused") {
# check that we can actually pause the service
if ($result.can_pause_and_continue -eq $false) {
Fail-Json $result "failed to pause service $($svc.Name): The service does not support pausing"
}
try {
$svc | Suspend-Service -WhatIf:$check_mode
} catch {
Fail-Json $result "failed to pause service $($svc.Name): $($_.Exception.Message)"
}
$result.changed = $true
}
if ($state -eq "absent") {
try {
$svc | Stop-Service -Force:$force_dependent_services -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
if (-not $check_mode) {
$return = $wmi_svc | Invoke-CimMethod -MethodName Delete
if ($return.ReturnValue -ne 0) {
$error_msg = Get-WmiErrorMessage -return_value $return.ReturnValue
Fail-Json -obj $result -message "Failed to delete service $($svc.Name): $($return.ReturnValue) - $error_msg"
}
}
$result.changed = $true
}
}
Function Set-ServiceConfiguration($svc) {
$wmi_svc = Get-CimInstance -ClassName Win32_Service -Filter "name='$($svc.Name)'"
Get-ServiceInfo -name $svc.Name
if ($desktop_interact -eq $true -and (-not ($result.username -eq 'LocalSystem' -or $username -eq 'LocalSystem'))) {
Fail-Json $result "Can only set desktop_interact to true when service is run with/or 'username' equals 'LocalSystem'"
}
if ($null -ne $start_mode) {
Set-ServiceStartMode -svc $svc -start_mode $start_mode
}
if ($null -ne $username) {
Set-ServiceAccount -wmi_svc $wmi_svc -username_sid $username_sid -username $username -password $password
}
if ($null -ne $display_name) {
Set-ServiceDisplayName -svc $svc -display_name $display_name
}
if ($null -ne $desktop_interact) {
Set-ServiceDesktopInteract -wmi_svc $wmi_svc -desktop_interact $desktop_interact
}
if ($null -ne $description) {
Set-ServiceDescription -svc $svc -description $description
}
if ($null -ne $path) {
Set-ServicePath -name $svc.Name -path $path
}
if ($null -ne $dependencies) {
Set-ServiceDependencies -wmi_svc $wmi_svc -dependency_action $dependency_action -dependencies $dependencies
}
if ($null -ne $state) {
Set-ServiceState -svc $svc -wmi_svc $wmi_svc -state $state
}
}
# need to use Where-Object as -Name doesn't work with [] in the service name
# https://github.com/ansible/ansible/issues/37621
$svc = Get-Service | Where-Object { $_.Name -eq $name -or $_.DisplayName -eq $name }
if ($svc) {
Set-ServiceConfiguration -svc $svc
} else {
$result.exists = $false
if ($state -ne 'absent') {
# Check if path is defined, if so create the service
if ($null -ne $path) {
try {
New-Service -Name $name -BinaryPathname $path -WhatIf:$check_mode
} catch {
Fail-Json $result $_.Exception.Message
}
$result.changed = $true
$svc = Get-Service | Where-Object { $_.Name -eq $name }
Set-ServiceConfiguration -svc $svc
} else {
# We will only reach here if the service is installed and the state is not absent
# Will check if any of the default actions are set and fail as we cannot action it
if ($null -ne $start_mode -or
$null -ne $state -or
$null -ne $username -or
$null -ne $password -or
$null -ne $display_name -or
$null -ne $description -or
$desktop_interact -ne $false -or
$null -ne $dependencies -or
$dependency_action -ne 'set') {
Fail-Json $result "Service '$name' is not installed, need to set 'path' to create a new service"
}
}
}
}
# After making a change, let's get the service info again unless we deleted it
if ($state -eq 'absent') {
# Recreate result so it doesn't have the extra meta data now that is has been deleted
$changed = $result.changed
$result = @{
changed = $changed
exists = $false
}
} elseif ($null -ne $svc) {
Get-ServiceInfo -name $name
}
Exit-Json -obj $result

@ -1,307 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2014, Chris Hoffman <choffman@chathamfinancial.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_service
version_added: '1.7'
short_description: Manage and query Windows services
description:
- Manage and query Windows services.
- For non-Windows targets, use the M(service) module instead.
options:
dependencies:
description:
- A list of service dependencies to set for this particular service.
- This should be a list of service names and not the display name of the
service.
- This works by C(dependency_action) to either add/remove or set the
services in this list.
type: list
version_added: '2.3'
dependency_action:
description:
- Used in conjunction with C(dependency) to either add the dependencies to
the existing service dependencies.
- Remove the dependencies to the existing dependencies.
- Set the dependencies to only the values in the list replacing the
existing dependencies.
type: str
choices: [ add, remove, set ]
default: set
version_added: '2.3'
desktop_interact:
description:
- Whether to allow the service user to interact with the desktop.
- This should only be set to C(yes) when using the C(LocalSystem) username.
type: bool
default: no
version_added: '2.3'
description:
description:
- The description to set for the service.
type: str
version_added: '2.3'
display_name:
description:
- The display name to set for the service.
type: str
version_added: '2.3'
force_dependent_services:
description:
- If C(yes), stopping or restarting a service with dependent services will
force the dependent services to stop or restart also.
- If C(no), stopping or restarting a service with dependent services may
fail.
type: bool
default: no
version_added: '2.3'
name:
description:
- Name of the service.
- If only the name parameter is specified, the module will report
on whether the service exists or not without making any changes.
required: yes
type: str
path:
description:
- The path to the executable to set for the service.
type: str
version_added: '2.3'
password:
description:
- The password to set the service to start as.
- This and the C(username) argument must be supplied together.
- If specifying C(LocalSystem), C(NetworkService) or C(LocalService) this field
must be an empty string and not null.
type: str
version_added: '2.3'
start_mode:
description:
- Set the startup type for the service.
- A newly created service will default to C(auto).
- C(delayed) added in Ansible 2.3
type: str
choices: [ auto, delayed, disabled, manual ]
state:
description:
- The desired state of the service.
- C(started)/C(stopped)/C(absent)/C(paused) are idempotent actions that will not run
commands unless necessary.
- C(restarted) will always bounce the service.
- C(absent) was added in Ansible 2.3
- C(paused) was added in Ansible 2.4
- Only services that support the paused state can be paused, you can
check the return value C(can_pause_and_continue).
- You can only pause a service that is already started.
- A newly created service will default to C(stopped).
type: str
choices: [ absent, paused, started, stopped, restarted ]
username:
description:
- The username to set the service to start as.
- This and the C(password) argument must be supplied together when using
a local or domain account.
- Set to C(LocalSystem) to use the SYSTEM account.
- A newly created service will default to C(LocalSystem).
- If using a custom user account, it must have the C(SeServiceLogonRight)
granted to be able to start up. You can use the M(win_user_right) module
to grant this user right for you.
type: str
version_added: '2.3'
seealso:
- module: service
- module: win_nssm
- module: win_user_right
author:
- Chris Hoffman (@chrishoffman)
'''
EXAMPLES = r'''
- name: Restart a service
win_service:
name: spooler
state: restarted
- name: Set service startup mode to auto and ensure it is started
win_service:
name: spooler
start_mode: auto
state: started
- name: Pause a service
win_service:
name: Netlogon
state: paused
- name: Ensure that WinRM is started when the system has settled
win_service:
name: WinRM
start_mode: delayed
# A new service will also default to the following values:
# - username: LocalSystem
# - state: stopped
# - start_mode: auto
- name: Create a new service
win_service:
name: service name
path: C:\temp\test.exe
- name: Create a new service with extra details
win_service:
name: service name
path: C:\temp\test.exe
display_name: Service Name
description: A test service description
- name: Remove a service
win_service:
name: service name
state: absent
- name: Check if a service is installed
win_service:
name: service name
register: service_info
# This is required to be set for non-service accounts that need to run as a service
- name: Grant domain account the SeServiceLogonRight user right
win_user_right:
name: SeServiceLogonRight
users:
- DOMAIN\User
action: add
- name: Set the log on user to a domain account
win_service:
name: service name
state: restarted
username: DOMAIN\User
password: Password
- name: Set the log on user to a local account
win_service:
name: service name
state: restarted
username: .\Administrator
password: Password
- name: Set the log on user to Local System
win_service:
name: service name
state: restarted
username: LocalSystem
password: ''
- name: Set the log on user to Local System and allow it to interact with the desktop
win_service:
name: service name
state: restarted
username: LocalSystem
password: ""
desktop_interact: yes
- name: Set the log on user to Network Service
win_service:
name: service name
state: restarted
username: NT AUTHORITY\NetworkService
password: ''
- name: Set the log on user to Local Service
win_service:
name: service name
state: restarted
username: NT AUTHORITY\LocalService
password: ''
- name: Set dependencies to ones only in the list
win_service:
name: service name
dependencies: [ service1, service2 ]
- name: Add dependencies to existing dependencies
win_service:
name: service name
dependencies: [ service1, service2 ]
dependency_action: add
- name: Remove dependencies from existing dependencies
win_service:
name: service name
dependencies:
- service1
- service2
dependency_action: remove
'''
RETURN = r'''
exists:
description: Whether the service exists or not.
returned: success
type: bool
sample: true
name:
description: The service name or id of the service.
returned: success and service exists
type: str
sample: CoreMessagingRegistrar
display_name:
description: The display name of the installed service.
returned: success and service exists
type: str
sample: CoreMessaging
state:
description: The current running status of the service.
returned: success and service exists
type: str
sample: stopped
start_mode:
description: The startup type of the service.
returned: success and service exists
type: str
sample: manual
path:
description: The path to the service executable.
returned: success and service exists
type: str
sample: C:\Windows\system32\svchost.exe -k LocalServiceNoNetwork
can_pause_and_continue:
description: Whether the service can be paused and unpaused.
returned: success and service exists
type: bool
sample: true
description:
description: The description of the service.
returned: success and service exists
type: str
sample: Manages communication between system components.
username:
description: The username that runs the service.
returned: success and service exists
type: str
sample: LocalSystem
desktop_interact:
description: Whether the current user is allowed to interact with the desktop.
returned: success and service exists
type: bool
sample: false
dependencies:
description: A list of services that is depended by this service.
returned: success and service exists
type: list
sample: false
depended_by:
description: A list of services that depend on this service.
returned: success and service exists
type: list
sample: false
'''

@ -1,207 +0,0 @@
#!powershell
# Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#AnsibleRequires -CSharpUtil Ansible.Service
$spec = @{
options = @{
name = @{ type = "str" }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$name = $module.Params.name
$module.Result.exists = $false
$module.Result.services = @(foreach ($rawService in (Get-Service -Name $name -ErrorAction SilentlyContinue)) {
try {
$service = New-Object -TypeName Ansible.Service.Service -ArgumentList @(
$rawService.Name, [Ansible.Service.ServiceRights]'EnumerateDependents, QueryConfig, QueryStatus'
)
} catch [Ansible.Service.ServiceManagerException] {
# ERROR_ACCESS_DENIED, ignore the service and continue on.
if ($_.Exception.InnerException -and $_.Exception.InnerException.NativeErrorCode -eq 5) {
$module.Warn("Failed to access service '$($rawService.Name) to get more info, ignoring")
continue
}
throw
}
$module.Result.exists = $true
$controlsAccepted = @($service.ControlsAccepted.ToString() -split ',' | ForEach-Object -Process {
switch ($_.Trim()) {
Stop { 'stop' }
PauseContinue { 'pause_continue' }
Shutdown { 'shutdown' }
ParamChange { 'param_change' }
NetbindChange { 'netbind_change' }
HardwareProfileChange { 'hardware_profile_change' }
PowerEvent { 'power_event' }
SessionChange { 'session_change' }
PreShutdown { 'pre_shutdown' }
}
})
$rawFailureActions = $service.FailureActions
$failureActions = @(foreach ($action in $rawFailureActions.Actions) {
[Ordered]@{
type = switch ($action.Type) {
None { 'none' }
Reboot { 'reboot' }
Restart { 'restart' }
RunCommand { 'run_command' }
}
delay_ms = $action.Delay
}
})
# LaunchProtection is only valid in Windows 8.1 (2012 R2) or above.
$launchProtection = 'none'
if ($service.LaunchProtection) {
$launchProtection = switch ($service.LaunchProtection) {
None { 'none' }
Windows { 'windows' }
WindowsLight { 'windows_light' }
AntimalwareLight { 'antimalware_light' }
}
}
$serviceFlags = @($service.ServiceFlags.ToString() -split ',' | ForEach-Object -Process {
switch ($_.Trim()) {
RunsInSystemProcess { 'runs_in_system_process' }
}
})
# The ServiceType value can contain other flags which are represented by other properties, this strips them out
# so we don't include them in the service_type return value.
$serviceType = [uint32]$service.ServiceType -band -bnot [uint32][Ansible.Service.ServiceType]::InteractiveProcess
$serviceType = $serviceType -band -bnot [uint32][Ansible.Service.ServiceType]::UserServiceInstance
$serviceType = switch (([Ansible.Service.ServiceType]$serviceType).ToString()) {
KernelDriver { 'kernel_driver' }
FileSystemDriver { 'file_system_driver' }
Adapter { 'adapter' }
RecognizerDriver { 'recognizer_driver' }
Win32OwnProcess { 'win32_own_process' }
Win32ShareProcess { 'win32_share_process' }
UserOwnprocess { 'user_own_process' }
UserShareProcess { 'user_share_process' }
PkgService { 'pkg_service' }
}
$startType = switch ($service.StartType) {
BootStart { 'boot_start' }
SystemStart { 'system_start' }
AutoStart { 'auto' }
DemandStart { 'manual' }
Disabled { 'disabled' }
AutoStartDelayed { 'delayed' }
}
$state = switch ($service.State) {
Stopped { 'stopped' }
StartPending { 'start_pending' }
StopPending { 'stop_pending' }
Running { 'started' }
ContinuePending { 'continue_pending' }
PausePending { 'pause_pending' }
paused { 'paused' }
}
$triggers = @(foreach ($trigger in $service.Triggers) {
[Ordered]@{
action = switch($trigger.Action) {
ServiceStart { 'start_service' }
ServiceStop { 'stop_service' }
}
type = switch($trigger.Type) {
DeviceInterfaceArrival { 'device_interface_arrival' }
IpAddressAvailability { 'ip_address_availability' }
DomainJoin { 'domain_join' }
FirewallPortEvent { 'firewall_port_event' }
GroupPolicy { 'group_policy' }
NetworkEndpoint { 'network_endpoint' }
Custom { 'custom' }
}
sub_type = switch($trigger.SubType.ToString()) {
([Ansible.Service.Trigger]::NAMED_PIPE_EVENT_GUID) { 'named_pipe_event' }
([Ansible.Service.Trigger]::RPC_INTERFACE_EVENT_GUID) { 'rpc_interface_event' }
([Ansible.Service.Trigger]::DOMAIN_JOIN_GUID) { 'domain_join' }
([Ansible.Service.Trigger]::DOMAIN_LEAVE_GUID) { 'domain_leave' }
([Ansible.Service.Trigger]::FIREWALL_PORT_OPEN_GUID) { 'firewall_port_open' }
([Ansible.Service.Trigger]::FIREWALL_PORT_CLOSE_GUID) { 'firewall_port_close' }
([Ansible.Service.Trigger]::MACHINE_POLICY_PRESENT_GUID) { 'machine_policy_present' }
([Ansible.Service.Trigger]::USER_POLICY_PRESENT_GUID) { 'user_policy_present' }
([Ansible.Service.Trigger]::NETWORK_MANAGER_FIRST_IP_ADDRESS_ARRIVAL_GUID) { 'network_first_ip_arrival' }
([Ansible.Service.Trigger]::NETWORK_MANAGER_LAST_IP_ADDRESS_REMOVAL_GUID) { 'network_last_ip_removal' }
default { 'custom' }
}
sub_type_guid = $trigger.SubType.ToString()
data_items = @(foreach ($dataItem in $trigger.DataItems) {
$dataValue = $dataItem.Data
# We only need to convert byte and byte[] to a Base64 string, the rest can be serialised as is.
if ($dataValue -is [byte]) {
$dataValue = [byte[]]@($dataValue)
}
if ($dataValue -is [byte[]]) {
$dataValue = [System.Convert]::ToBase64String($dataValue)
}
[Ordered]@{
type = switch ($dataItem.Type) {
Binary { 'binary' }
String { 'string' }
Level { 'level' }
KeywordAny { 'keyword_any' }
KeywordAll { 'keyword_all' }
}
data = $dataValue
}
})
}
})
# These should closely reflect the options for win_service
[Ordered]@{
checkpoint = $service.Checkpoint
controls_accepted = $controlsAccepted
dependencies = $service.DependentOn
dependency_of = $service.DependedBy
description = $service.Description
desktop_interact = $service.ServiceType.HasFlag([Ansible.Service.ServiceType]::InteractiveProcess)
display_name = $service.DisplayName
error_control = $service.ErrorControl.ToString().ToLowerInvariant()
failure_actions = $failureActions
failure_actions_on_non_crash_failure = $service.FailureActionsOnNonCrashFailures
failure_command = $rawFailureActions.Command
failure_reboot_msg = $rawFailureActions.RebootMsg
failure_reset_period_sec = $rawFailureActions.ResetPeriod
launch_protection = $launchProtection
load_order_group = $service.LoadOrderGroup
name = $service.ServiceName
path = $service.Path
pre_shutdown_timeout_ms = $service.PreShutdownTimeout
preferred_node = $service.PreferredNode
process_id = $service.ProcessId
required_privileges = $service.RequiredPrivileges
service_exit_code = $service.ServiceExitCode
service_flags = $serviceFlags
service_type = $serviceType
sid_info = $service.ServiceSidInfo.ToString().ToLowerInvariant()
start_mode = $startType
state = $state
triggers = $triggers
username = $service.Account.Value
wait_hint_ms = $service.WaitHint
win32_exit_code = $service.Win32ExitCode
}
})
$module.ExitJson()

@ -1,294 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_service_info
version_added: '2.10'
short_description: Gather information about Windows services
description:
- Gather information about all or a specific installed Windows service(s).
options:
name:
description:
- If specified, this is used to match the C(name) or C(display_name) of the Windows service to get the info for.
- Can be a wildcard to match multiple services but the wildcard will only be matched on the C(name) of the service
and not C(display_name).
- If omitted then all services will returned.
type: str
seealso:
- module: win_service
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Get info for all installed services
win_service_info:
register: service_info
- name: Get info for a single service
win_service_info:
name: WinRM
register: service_info
- name: Get info for a service using its display name
win_service_info:
name: Windows Remote Management (WS-Management)
- name: Find all services that start with 'win'
win_service_info:
name: win*
'''
RETURN = r'''
exists:
description: Whether any services were found based on the criteria specified.
returned: always
type: bool
sample: true
services:
description:
- A list of service(s) that were found based on the criteria.
- Will be an empty list if no services were found.
returned: always
type: list
elements: dict
contains:
checkpoint:
description:
- A check-point value that the service increments periodically to report its progress.
type: int
sample: 0
controls_accepted:
description:
- A list of controls that the service can accept.
- Common controls are C(stop), C(pause_continue), C(shutdown).
type: list
elements: str
sample: ['stop', 'shutdown']
dependencies:
description:
- A list of services by their C(name) that this service is dependent on.
type: list
elements: str
sample: ['HTTP', 'RPCSS']
dependency_of:
description:
- A list of services by their C(name) that depend on this service.
type: list
elements: str
sample: ['upnphost', 'WMPNetworkSvc']
description:
description:
- The description of the service.
type: str
sample: Example description of the Windows service.
desktop_interact:
description:
- Whether the service can interact with the desktop, only valid for services running as C(SYSTEM).
type: bool
sample: false
display_name:
description:
- The display name to be used by SCM to identify the service.
type: str
sample: Windows Remote Management (WS-Management)
error_control:
description:
- The action to take if a service fails to start.
- Common values are C(critical), C(ignore), C(normal), C(severe).
type: str
sample: normal
failure_actions:
description:
- A list of failure actions to run in the event of a failure.
type: list
elements: dict
contains:
delay_ms:
description:
- The time to wait, in milliseconds, before performing the specified action.
type: int
sample: 120000
type:
description:
- The action that will be performed.
- Common values are C(none), C(reboot), C(restart), C(run_command).
type: str
sample: run_command
failure_action_on_non_crash_failure:
description:
- Controls when failure actions are fired based on how the service was stopped.
type: bool
sample: false
failure_command:
description:
- The command line that will be run when a C(run_command) failure action is fired.
type: str
sample: runme.exe
failure_reboot_msg:
description:
- The message to be broadcast to server users before rebooting when a C(reboot) failure action is fired.
type: str
sample: Service failed, rebooting host.
failure_reset_period_sec:
description:
- The time, in seconds, after which to reset the failure count to zero.
type: int
sample: 86400
launch_protection:
description:
- The protection type of the service.
- Common values are C(none), C(windows), C(windows_light), or C(antimalware_light).
type: str
sample: none
load_order_group:
description:
- The name of the load ordering group to which the service belongs.
- Will be an empty string if it does not belong to any group.
type: str
sample: My group
name:
description:
- The name of the service.
type: str
sample: WinRM
path:
description:
- The path to the service binary and any arguments used when starting the service.
- The binary part can be quoted to ensure any spaces in path are not treated as arguments.
type: str
sample: 'C:\Windows\System32\svchost.exe -k netsvcs -p'
pre_shutdown_timeout_ms:
description:
- The preshutdown timeout out value in milliseconds.
type: int
sample: 10000
preferred_node:
description:
- The node number for the preferred node.
- This will be C(null) if the Windows host has no NUMA configuration.
type: int
sample: 0
process_id:
description:
- The process identifier of the running service.
type: int
sample: 5135
required_privileges:
description:
- A list of privileges that the service requires and will run with
type: list
elements: str
sample: ['SeBackupPrivilege', 'SeRestorePrivilege']
service_exit_code:
description:
- A service-specific error code that is set while the service is starting or stopping.
type: int
sample: 0
service_flags:
description:
- Shows more information about the behaviour of a running service.
- Currently the only flag that can be set is C(runs_in_system_process).
type: list
elements: str
sample: [ 'runs_in_system_process' ]
service_type:
description:
- The type of service.
- Common types are C(win32_own_process), C(win32_share_process), C(user_own_process), C(user_share_process),
C(kernel_driver).
type: str
sample: win32_own_process
sid_info:
description:
- The behavior of how the service's access token is generated and how to add the service SID to the token.
- Common values are C(none), C(restricted), or C(unrestricted).
type: str
sample: none
start_mode:
description:
- When the service is set to start.
- Common values are C(auto), C(manual), C(disabled), C(delayed).
type: str
sample: auto
state:
description:
- The current running state of the service.
- Common values are C(stopped), C(start_pending), C(stop_pending), C(started), C(continue_pending),
C(pause_pending), C(paused).
type: str
sample: started
triggers:
description:
- A list of triggers defined for the service.
type: list
elements: dict
contains:
action:
description:
- The action to perform once triggered, can be C(start_service) or C(stop_service).
type: str
sample: start_service
data_items:
description:
- A list of trigger data items that contain trigger specific data.
- A trigger can contain 0 or multiple data items.
type: list
elements: dict
contains:
data:
description:
- The trigger data item value.
- Can be a string, list of string, int, or base64 string of binary data.
type: complex
sample: named pipe
type:
description:
- The type of C(data) for the trigger.
- Common values are C(string), C(binary), C(level), C(keyword_any), or C(keyword_all).
type: str
sample: string
sub_type:
description:
- The trigger event sub type that is specific to each C(type).
- Common values are C(named_pipe_event), C(domain_join), C(domain_leave), C(firewall_port_open), and others.
type: str
sample:
sub_type_guid:
description:
- The guid which represents the trigger sub type.
type: str
sample: 1ce20aba-9851-4421-9430-1ddeb766e809
type:
description:
- The trigger event type.
- Common values are C(custom), C(rpc_interface_event), C(domain_join), C(group_policy), and others.
type: str
sample: domain_join
username:
description:
- The username used to run the service.
- Can be null for user services and certain driver services.
type: str
sample: NT AUTHORITY\SYSTEM
wait_hint_ms:
description:
- The estimated time in milliseconds required for a pending start, stop, pause,or continue operations.
type: int
sample: 0
win32_exitcode:
description:
- The error code returned from the service binary once it has stopped.
- When set to C(1066) then a service specific error is returned on C(service_exit_code).
type: int
sample: 0
'''

@ -1,267 +0,0 @@
#!powershell
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.SID
#Functions
Function NormalizeAccounts {
param(
[parameter(valuefrompipeline=$true)]
$users
)
$users = $users.Trim()
If ($users -eq "") {
$splitUsers = [Collections.Generic.List[String]] @()
}
Else {
$splitUsers = [Collections.Generic.List[String]] $users.Split(",")
}
$normalizedUsers = [Collections.Generic.List[String]] @()
ForEach($splitUser in $splitUsers) {
$sid = Convert-ToSID -account_name $splitUser
if (!$sid) {
Fail-Json $result "$splitUser is not a valid user or group on the host machine or domain"
}
$normalizedUser = (New-Object System.Security.Principal.SecurityIdentifier($sid)).Translate([System.Security.Principal.NTAccount])
$normalizedUsers.Add($normalizedUser)
}
return ,$normalizedUsers
}
$result = @{
changed = $false
actions = @() # More for debug purposes
}
$params = Parse-Args $args -supports_check_mode $true
# While the -SmbShare cmdlets have a -WhatIf parameter, they don't honor it, need to skip the cmdlet if in check mode
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent"
$rule_action = Get-AnsibleParam -obj $params -name "rule_action" -type "str" -default "set" -validateset "set","add"
if (-not (Get-Command -Name Get-SmbShare -ErrorAction SilentlyContinue)) {
Fail-Json $result "The current host does not support the -SmbShare cmdlets required by this module. Please run on Server 2012 or Windows 8 and later"
}
$share = Get-SmbShare -Name $name -ErrorAction SilentlyContinue
If ($state -eq "absent") {
If ($share) {
# See message around -WhatIf where $check_mode is defined
if (-not $check_mode) {
Remove-SmbShare -Force -Name $name | Out-Null
}
$result.actions += "Remove-SmbShare -Force -Name $name"
$result.changed = $true
}
} Else {
$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true
$description = Get-AnsibleParam -obj $params -name "description" -type "str" -default ""
$permissionList = Get-AnsibleParam -obj $params -name "list" -type "bool" -default $false
$folderEnum = if ($permissionList) { "Unrestricted" } else { "AccessBased" }
$permissionRead = Get-AnsibleParam -obj $params -name "read" -type "str" -default "" | NormalizeAccounts
$permissionChange = Get-AnsibleParam -obj $params -name "change" -type "str" -default "" | NormalizeAccounts
$permissionFull = Get-AnsibleParam -obj $params -name "full" -type "str" -default "" | NormalizeAccounts
$permissionDeny = Get-AnsibleParam -obj $params -name "deny" -type "str" -default "" | NormalizeAccounts
$cachingMode = Get-AnsibleParam -obj $params -name "caching_mode" -type "str" -default "Manual" -validateSet "BranchCache","Documents","Manual","None","Programs","Unknown"
$encrypt = Get-AnsibleParam -obj $params -name "encrypt" -type "bool" -default $false
If (-Not (Test-Path -Path $path)) {
Fail-Json $result "$path directory does not exist on the host"
}
# normalize path and remove slash at the end
$path = (Get-Item $path).FullName -replace "\\$"
# need to (re-)create share
If (-not $share) {
if (-not $check_mode) {
New-SmbShare -Name $name -Path $path | Out-Null
}
$share = Get-SmbShare -Name $name -ErrorAction SilentlyContinue
$result.changed = $true
$result.actions += "New-SmbShare -Name $name -Path $path"
# if in check mode we cannot run the below as no share exists so just
# exit early
if ($check_mode) {
Exit-Json -obj $result
}
}
If ($share.Path -ne $path) {
if (-not $check_mode) {
Remove-SmbShare -Force -Name $name | Out-Null
New-SmbShare -Name $name -Path $path | Out-Null
}
$share = Get-SmbShare -Name $name -ErrorAction SilentlyContinue
$result.changed = $true
$result.actions += "Remove-SmbShare -Force -Name $name"
$result.actions += "New-SmbShare -Name $name -Path $path"
}
# updates
If ($share.Description -ne $description) {
if (-not $check_mode) {
Set-SmbShare -Force -Name $name -Description $description | Out-Null
}
$result.changed = $true
$result.actions += "Set-SmbShare -Force -Name $name -Description $description"
}
If ($share.FolderEnumerationMode -ne $folderEnum) {
if (-not $check_mode) {
Set-SmbShare -Force -Name $name -FolderEnumerationMode $folderEnum | Out-Null
}
$result.changed = $true
$result.actions += "Set-SmbShare -Force -Name $name -FolderEnumerationMode $folderEnum"
}
if ($share.CachingMode -ne $cachingMode) {
if (-not $check_mode) {
Set-SmbShare -Force -Name $name -CachingMode $cachingMode | Out-Null
}
$result.changed = $true
$result.actions += "Set-SmbShare -Force -Name $name -CachingMode $cachingMode"
}
if ($share.EncryptData -ne $encrypt) {
if (-not $check_mode) {
Set-SmbShare -Force -Name $name -EncryptData $encrypt | Out-Null
}
$result.changed = $true
$result.actions += "Set-SmbShare -Force -Name $name -EncryptData $encrypt"
}
# clean permissions that imply others
ForEach ($user in $permissionFull) {
$permissionChange.remove($user) | Out-Null
$permissionRead.remove($user) | Out-Null
}
ForEach ($user in $permissionChange) {
$permissionRead.remove($user) | Out-Null
}
# remove permissions
$permissions = Get-SmbShareAccess -Name $name
if($rule_action -eq "set") {
ForEach ($permission in $permissions) {
If ($permission.AccessControlType -eq "Deny") {
$cim_count = 0
foreach ($count in $permissions) {
$cim_count++
}
# Don't remove the Deny entry for Everyone if there are no other permissions set (cim_count == 1)
if (-not ($permission.AccountName -eq 'Everyone' -and $cim_count -eq 1)) {
If (-not ($permissionDeny.Contains($permission.AccountName))) {
if (-not $check_mode) {
Unblock-SmbShareAccess -Force -Name $name -AccountName $permission.AccountName | Out-Null
}
$result.changed = $true
$result.actions += "Unblock-SmbShareAccess -Force -Name $name -AccountName $($permission.AccountName)"
} else {
# Remove from the deny list as it already has the permissions
$permissionDeny.remove($permission.AccountName) | Out-Null
}
}
} ElseIf ($permission.AccessControlType -eq "Allow") {
If ($permission.AccessRight -eq "Full") {
If (-not ($permissionFull.Contains($permission.AccountName))) {
if (-not $check_mode) {
Revoke-SmbShareAccess -Force -Name $name -AccountName $permission.AccountName | Out-Null
}
$result.changed = $true
$result.actions += "Revoke-SmbShareAccess -Force -Name $name -AccountName $($permission.AccountName)"
Continue
}
# user got requested permissions
$permissionFull.remove($permission.AccountName) | Out-Null
} ElseIf ($permission.AccessRight -eq "Change") {
If (-not ($permissionChange.Contains($permission.AccountName))) {
if (-not $check_mode) {
Revoke-SmbShareAccess -Force -Name $name -AccountName $permission.AccountName | Out-Null
}
$result.changed = $true
$result.actions += "Revoke-SmbShareAccess -Force -Name $name -AccountName $($permission.AccountName)"
Continue
}
# user got requested permissions
$permissionChange.remove($permission.AccountName) | Out-Null
} ElseIf ($permission.AccessRight -eq "Read") {
If (-not ($permissionRead.Contains($permission.AccountName))) {
if (-not $check_mode) {
Revoke-SmbShareAccess -Force -Name $name -AccountName $permission.AccountName | Out-Null
}
$result.changed = $true
$result.actions += "Revoke-SmbShareAccess -Force -Name $name -AccountName $($permission.AccountName)"
Continue
}
# user got requested permissions
$permissionRead.Remove($permission.AccountName) | Out-Null
}
}
}
} ElseIf ($rule_action -eq "add") {
ForEach($permission in $permissions) {
If ($permission.AccessControlType -eq "Deny") {
If ($permissionDeny.Contains($permission.AccountName)) {
$permissionDeny.Remove($permission.AccountName)
}
} ElseIf ($permission.AccessControlType -eq "Allow") {
If ($permissionFull.Contains($permission.AccountName) -and $permission.AccessRight -eq "Full") {
$permissionFull.Remove($permission.AccountName)
} ElseIf ($permissionChange.Contains($permission.AccountName) -and $permission.AccessRight -eq "Change") {
$permissionChange.Remove($permission.AccountName)
} ElseIf ($permissionRead.Contains($permission.AccountName) -and $permission.AccessRight -eq "Read") {
$permissionRead.Remove($permission.AccountName)
}
}
}
}
# add missing permissions
ForEach ($user in $permissionRead) {
if (-not $check_mode) {
Grant-SmbShareAccess -Force -Name $name -AccountName $user -AccessRight "Read" | Out-Null
}
$result.changed = $true
$result.actions += "Grant-SmbShareAccess -Force -Name $name -AccountName $user -AccessRight Read"
}
ForEach ($user in $permissionChange) {
if (-not $check_mode) {
Grant-SmbShareAccess -Force -Name $name -AccountName $user -AccessRight "Change" | Out-Null
}
$result.changed = $true
$result.actions += "Grant-SmbShareAccess -Force -Name $name -AccountName $user -AccessRight Change"
}
ForEach ($user in $permissionFull) {
if (-not $check_mode) {
Grant-SmbShareAccess -Force -Name $name -AccountName $user -AccessRight "Full" | Out-Null
}
$result.changed = $true
$result.actions += "Grant-SmbShareAccess -Force -Name $name -AccountName $user -AccessRight Full"
}
ForEach ($user in $permissionDeny) {
if (-not $check_mode) {
Block-SmbShareAccess -Force -Name $name -AccountName $user | Out-Null
}
$result.changed = $true
$result.actions += "Block-SmbShareAccess -Force -Name $name -AccountName $user"
}
}
Exit-Json $result

@ -1,125 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Hans-Joachim Kliemeck <git@kliemeck.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_share
version_added: "2.1"
short_description: Manage Windows shares
description:
- Add, modify or remove Windows share and set share permissions.
requirements:
- As this module used newer cmdlets like New-SmbShare this can only run on
Windows 8 / Windows 2012 or newer.
- This is due to the reliance on the WMI provider MSFT_SmbShare
U(https://msdn.microsoft.com/en-us/library/hh830471) which was only added
with these Windows releases.
options:
name:
description:
- Share name.
type: str
required: yes
path:
description:
- Share directory.
type: path
required: yes
state:
description:
- Specify whether to add C(present) or remove C(absent) the specified share.
type: str
choices: [ absent, present ]
default: present
description:
description:
- Share description.
type: str
list:
description:
- Specify whether to allow or deny file listing, in case user has no permission on share. Also known as Access-Based Enumeration.
type: bool
default: no
read:
description:
- Specify user list that should get read access on share, separated by comma.
type: str
change:
description:
- Specify user list that should get read and write access on share, separated by comma.
type: str
full:
description:
- Specify user list that should get full access on share, separated by comma.
type: str
deny:
description:
- Specify user list that should get no access, regardless of implied access on share, separated by comma.
type: str
caching_mode:
description:
- Set the CachingMode for this share.
type: str
choices: [ BranchCache, Documents, Manual, None, Programs, Unknown ]
default: Manual
version_added: "2.3"
encrypt:
description: Sets whether to encrypt the traffic to the share or not.
type: bool
default: no
version_added: "2.4"
rule_action:
description: Whether to add or set (replace) access control entries.
type: str
choices: [ set, add ]
default: set
version_added: "2.10"
author:
- Hans-Joachim Kliemeck (@h0nIg)
- David Baumann (@daBONDi)
- Shachaf Goldstein (@Shachaf92)
'''
EXAMPLES = r'''
# Playbook example
# Add share and set permissions
---
- name: Add secret share
win_share:
name: internal
description: top secret share
path: C:\shares\internal
list: no
full: Administrators,CEO
read: HR-Global
deny: HR-External
- name: Add public company share
win_share:
name: company
description: top secret share
path: C:\shares\company
list: yes
full: Administrators,CEO
read: Global
- name: Remove previously added share
win_share:
name: internal
state: absent
'''
RETURN = r'''
actions:
description: A list of action cmdlets that were run by the module.
returned: success
type: list
sample: ['New-SmbShare -Name share -Path C:\temp']
'''

@ -1,138 +0,0 @@
#!powershell
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.CommandUtil
#Requires -Module Ansible.ModuleUtils.FileUtil
# TODO: add check mode support
Set-StrictMode -Version 2
$ErrorActionPreference = "Stop"
# Cleanse CLIXML from stderr (sift out error stream data, discard others for now)
Function Cleanse-Stderr($raw_stderr) {
Try {
# NB: this regex isn't perfect, but is decent at finding CLIXML amongst other stderr noise
If($raw_stderr -match "(?s)(?<prenoise1>.*)#< CLIXML(?<prenoise2>.*)(?<clixml><Objs.+</Objs>)(?<postnoise>.*)") {
$clixml = [xml]$matches["clixml"]
$merged_stderr = "{0}{1}{2}{3}" -f @(
$matches["prenoise1"],
$matches["prenoise2"],
# filter out just the Error-tagged strings for now, and zap embedded CRLF chars
($clixml.Objs.ChildNodes | Where-Object { $_.Name -eq 'S' } | Where-Object { $_.S -eq 'Error' } | ForEach-Object { $_.'#text'.Replace('_x000D__x000A_','') } | Out-String),
$matches["postnoise"]) | Out-String
return $merged_stderr.Trim()
# FUTURE: parse/return other streams
}
Else {
$raw_stderr
}
}
Catch {
"***EXCEPTION PARSING CLIXML: $_***" + $raw_stderr
}
}
$params = Parse-Args $args -supports_check_mode $false
$raw_command_line = Get-AnsibleParam -obj $params -name "_raw_params" -type "str" -failifempty $true
$chdir = Get-AnsibleParam -obj $params -name "chdir" -type "path"
$executable = Get-AnsibleParam -obj $params -name "executable" -type "path"
$creates = Get-AnsibleParam -obj $params -name "creates" -type "path"
$removes = Get-AnsibleParam -obj $params -name "removes" -type "path"
$stdin = Get-AnsibleParam -obj $params -name "stdin" -type "str"
$no_profile = Get-AnsibleParam -obj $params -name "no_profile" -type "bool" -default $false
$output_encoding_override = Get-AnsibleParam -obj $params -name "output_encoding_override" -type "str"
$raw_command_line = $raw_command_line.Trim()
$result = @{
changed = $true
cmd = $raw_command_line
}
if ($creates -and $(Test-AnsiblePath -Path $creates)) {
Exit-Json @{msg="skipped, since $creates exists";cmd=$raw_command_line;changed=$false;skipped=$true;rc=0}
}
if ($removes -and -not $(Test-AnsiblePath -Path $removes)) {
Exit-Json @{msg="skipped, since $removes does not exist";cmd=$raw_command_line;changed=$false;skipped=$true;rc=0}
}
$exec_args = $null
If(-not $executable -or $executable -eq "powershell") {
$exec_application = "powershell.exe"
# force input encoding to preamble-free UTF8 so PS sub-processes (eg, Start-Job) don't blow up
$raw_command_line = "[Console]::InputEncoding = New-Object Text.UTF8Encoding `$false; " + $raw_command_line
# Base64 encode the command so we don't have to worry about the various levels of escaping
$encoded_command = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($raw_command_line))
if ($stdin) {
$exec_args = "-encodedcommand $encoded_command"
} else {
$exec_args = "-noninteractive -encodedcommand $encoded_command"
}
if ($no_profile) {
$exec_args = "-noprofile $exec_args"
}
}
Else {
# FUTURE: support arg translation from executable (or executable_args?) to process arguments for arbitrary interpreter?
$exec_application = $executable
if (-not ($exec_application.EndsWith(".exe"))) {
$exec_application = "$($exec_application).exe"
}
$exec_args = "/c $raw_command_line"
}
$command = "`"$exec_application`" $exec_args"
$run_command_arg = @{
command = $command
}
if ($chdir) {
$run_command_arg['working_directory'] = $chdir
}
if ($stdin) {
$run_command_arg['stdin'] = $stdin
}
if ($output_encoding_override) {
$run_command_arg['output_encoding_override'] = $output_encoding_override
}
$start_datetime = [DateTime]::UtcNow
try {
$command_result = Run-Command @run_command_arg
} catch {
$result.changed = $false
try {
$result.rc = $_.Exception.NativeErrorCode
} catch {
$result.rc = 2
}
Fail-Json -obj $result -message $_.Exception.Message
}
# TODO: decode CLIXML stderr output (and other streams?)
$result.stdout = $command_result.stdout
$result.stderr = Cleanse-Stderr $command_result.stderr
$result.rc = $command_result.rc
$end_datetime = [DateTime]::UtcNow
$result.start = $start_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff")
$result.end = $end_datetime.ToString("yyyy-MM-dd hh:mm:ss.ffffff")
$result.delta = $($end_datetime - $start_datetime).ToString("h\:mm\:ss\.ffffff")
If ($result.rc -ne 0) {
Fail-Json -obj $result -message "non-zero return code"
}
Exit-Json $result

@ -1,167 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2016, Ansible, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_shell
short_description: Execute shell commands on target hosts
version_added: 2.2
description:
- The C(win_shell) module takes the command name followed by a list of space-delimited arguments.
It is similar to the M(win_command) module, but runs
the command via a shell (defaults to PowerShell) on the target host.
- For non-Windows targets, use the M(shell) module instead.
options:
free_form:
description:
- The C(win_shell) module takes a free form command to run.
- There is no parameter actually named 'free form'. See the examples!
type: str
required: yes
creates:
description:
- A path or path filter pattern; when the referenced path exists on the target host, the task will be skipped.
type: path
removes:
description:
- A path or path filter pattern; when the referenced path B(does not) exist on the target host, the task will be skipped.
type: path
chdir:
description:
- Set the specified path as the current working directory before executing a command
type: path
executable:
description:
- Change the shell used to execute the command (eg, C(cmd)).
- The target shell must accept a C(/c) parameter followed by the raw command line to be executed.
type: path
stdin:
description:
- Set the stdin of the command directly to the specified value.
type: str
version_added: '2.5'
no_profile:
description:
- Do not load the user profile before running a command. This is only valid
when using PowerShell as the executable.
type: bool
default: no
version_added: '2.8'
output_encoding_override:
description:
- This option overrides the encoding of stdout/stderr output.
- You can use this option when you need to run a command which ignore the console's codepage.
- You should only need to use this option in very rare circumstances.
- This value can be any valid encoding C(Name) based on the output of C([System.Text.Encoding]::GetEncodings()).
See U(https://docs.microsoft.com/dotnet/api/system.text.encoding.getencodings).
type: str
version_added: '2.10'
notes:
- If you want to run an executable securely and predictably, it may be
better to use the M(win_command) module instead. Best practices when writing
playbooks will follow the trend of using M(win_command) unless C(win_shell) is
explicitly required. When running ad-hoc commands, use your best judgement.
- WinRM will not return from a command execution until all child processes created have exited.
Thus, it is not possible to use C(win_shell) to spawn long-running child or background processes.
Consider creating a Windows service for managing background processes.
seealso:
- module: psexec
- module: raw
- module: script
- module: shell
- module: win_command
- module: win_psexec
author:
- Matt Davis (@nitzmahone)
'''
EXAMPLES = r'''
# Execute a command in the remote shell; stdout goes to the specified
# file on the remote.
- win_shell: C:\somescript.ps1 >> C:\somelog.txt
# Change the working directory to somedir/ before executing the command.
- win_shell: C:\somescript.ps1 >> C:\somelog.txt chdir=C:\somedir
# You can also use the 'args' form to provide the options. This command
# will change the working directory to somedir/ and will only run when
# somedir/somelog.txt doesn't exist.
- win_shell: C:\somescript.ps1 >> C:\somelog.txt
args:
chdir: C:\somedir
creates: C:\somelog.txt
# Run a command under a non-Powershell interpreter (cmd in this case)
- win_shell: echo %HOMEDIR%
args:
executable: cmd
register: homedir_out
- name: Run multi-lined shell commands
win_shell: |
$value = Test-Path -Path C:\temp
if ($value) {
Remove-Item -Path C:\temp -Force
}
New-Item -Path C:\temp -ItemType Directory
- name: Retrieve the input based on stdin
win_shell: '$string = [Console]::In.ReadToEnd(); Write-Output $string.Trim()'
args:
stdin: Input message
'''
RETURN = r'''
msg:
description: Changed.
returned: always
type: bool
sample: true
start:
description: The command execution start time.
returned: always
type: str
sample: '2016-02-25 09:18:26.429568'
end:
description: The command execution end time.
returned: always
type: str
sample: '2016-02-25 09:18:26.755339'
delta:
description: The command execution delta time.
returned: always
type: str
sample: '0:00:00.325771'
stdout:
description: The command standard output.
returned: always
type: str
sample: 'Clustering node rabbit@slave1 with rabbit@master ...'
stderr:
description: The command standard error.
returned: always
type: str
sample: 'ls: cannot access foo: No such file or directory'
cmd:
description: The command executed by the task.
returned: always
type: str
sample: 'rabbitmqctl join_cluster rabbit@master'
rc:
description: The command return code (0 means success).
returned: always
type: int
sample: 0
stdout_lines:
description: The command standard output split in lines.
returned: always
type: list
sample: [u'Clustering node rabbit@slave1 with rabbit@master ...']
'''

@ -1,186 +0,0 @@
#!powershell
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.FileUtil
#Requires -Module Ansible.ModuleUtils.LinkUtil
function ConvertTo-Timestamp($start_date, $end_date) {
if ($start_date -and $end_date) {
return (New-TimeSpan -Start $start_date -End $end_date).TotalSeconds
}
}
function Get-FileChecksum($path, $algorithm) {
switch ($algorithm) {
'md5' { $sp = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider }
'sha1' { $sp = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider }
'sha256' { $sp = New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider }
'sha384' { $sp = New-Object -TypeName System.Security.Cryptography.SHA384CryptoServiceProvider }
'sha512' { $sp = New-Object -TypeName System.Security.Cryptography.SHA512CryptoServiceProvider }
default { Fail-Json -obj $result -message "Unsupported hash algorithm supplied '$algorithm'" }
}
$fp = [System.IO.File]::Open($path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
try {
$hash = [System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower()
} finally {
$fp.Dispose()
}
return $hash
}
function Get-FileInfo {
param([String]$Path, [Switch]$Follow)
$info = Get-AnsibleItem -Path $Path -ErrorAction SilentlyContinue
$link_info = $null
if ($null -ne $info) {
try {
$link_info = Get-Link -link_path $info.FullName
} catch {
$module.Warn("Failed to check/get link info for file: $($_.Exception.Message)")
}
# If follow=true we want to follow the link all the way back to root object
if ($Follow -and $null -ne $link_info -and $link_info.Type -in @("SymbolicLink", "JunctionPoint")) {
$info, $link_info = Get-FileInfo -Path $link_info.AbsolutePath -Follow
}
}
return $info, $link_info
}
$spec = @{
options = @{
path = @{ type='path'; required=$true; aliases=@( 'dest', 'name' ) }
get_checksum = @{ type='bool'; default=$true }
checksum_algorithm = @{ type='str'; default='sha1'; choices=@( 'md5', 'sha1', 'sha256', 'sha384', 'sha512' ) }
follow = @{ type='bool'; default=$false }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$path = $module.Params.path
$get_checksum = $module.Params.get_checksum
$checksum_algorithm = $module.Params.checksum_algorithm
$follow = $module.Params.follow
$module.Result.stat = @{ exists=$false }
Load-LinkUtils
$info, $link_info = Get-FileInfo -Path $path -Follow:$follow
If ($null -ne $info) {
$epoch_date = Get-Date -Date "01/01/1970"
$attributes = @()
foreach ($attribute in ($info.Attributes -split ',')) {
$attributes += $attribute.Trim()
}
# default values that are always set, specific values are set below this
# but are kept commented for easier readability
$stat = @{
exists = $true
attributes = $info.Attributes.ToString()
isarchive = ($attributes -contains "Archive")
isdir = $false
ishidden = ($attributes -contains "Hidden")
isjunction = $false
islnk = $false
isreadonly = ($attributes -contains "ReadOnly")
isreg = $false
isshared = $false
nlink = 1 # Number of links to the file (hard links), overriden below if islnk
# lnk_target = islnk or isjunction Target of the symlink. Note that relative paths remain relative
# lnk_source = islnk os isjunction Target of the symlink normalized for the remote filesystem
hlnk_targets = @()
creationtime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.CreationTime)
lastaccesstime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.LastAccessTime)
lastwritetime = (ConvertTo-Timestamp -start_date $epoch_date -end_date $info.LastWriteTime)
# size = a file and directory - calculated below
path = $info.FullName
filename = $info.Name
# extension = a file
# owner = set outsite this dict in case it fails
# sharename = a directory and isshared is True
# checksum = a file and get_checksum: True
}
try {
$stat.owner = $info.GetAccessControl().Owner
} catch {
# may not have rights, historical behaviour was to just set to $null
# due to ErrorActionPreference being set to "Continue"
$stat.owner = $null
}
# values that are set according to the type of file
if ($info.Attributes.HasFlag([System.IO.FileAttributes]::Directory)) {
$stat.isdir = $true
$share_info = Get-CimInstance -ClassName Win32_Share -Filter "Path='$($stat.path -replace '\\', '\\')'"
if ($null -ne $share_info) {
$stat.isshared = $true
$stat.sharename = $share_info.Name
}
try {
$size = 0
foreach ($file in $info.EnumerateFiles("*", [System.IO.SearchOption]::AllDirectories)) {
$size += $file.Length
}
$stat.size = $size
} catch {
$stat.size = 0
}
} else {
$stat.extension = $info.Extension
$stat.isreg = $true
$stat.size = $info.Length
if ($get_checksum) {
try {
$stat.checksum = Get-FileChecksum -path $path -algorithm $checksum_algorithm
} catch {
$module.FailJson("Failed to get hash of file, set get_checksum to False to ignore this error: $($_.Exception.Message)", $_)
}
}
}
# Get symbolic link, junction point, hard link info
if ($null -ne $link_info) {
switch ($link_info.Type) {
"SymbolicLink" {
$stat.islnk = $true
$stat.isreg = $false
$stat.lnk_target = $link_info.TargetPath
$stat.lnk_source = $link_info.AbsolutePath
break
}
"JunctionPoint" {
$stat.isjunction = $true
$stat.isreg = $false
$stat.lnk_target = $link_info.TargetPath
$stat.lnk_source = $link_info.AbsolutePath
break
}
"HardLink" {
$stat.lnk_type = "hard"
$stat.nlink = $link_info.HardTargets.Count
# remove current path from the targets
$hlnk_targets = $link_info.HardTargets | Where-Object { $_ -ne $stat.path }
$stat.hlnk_targets = @($hlnk_targets)
break
}
}
}
$module.Result.stat = $stat
}
$module.ExitJson()

@ -1,236 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_stat
version_added: "1.7"
short_description: Get information about Windows files
description:
- Returns information about a Windows file.
- For non-Windows targets, use the M(stat) module instead.
options:
path:
description:
- The full path of the file/object to get the facts of; both forward and
back slashes are accepted.
type: path
required: yes
aliases: [ dest, name ]
get_checksum:
description:
- Whether to return a checksum of the file (default sha1)
type: bool
default: yes
version_added: "2.1"
checksum_algorithm:
description:
- Algorithm to determine checksum of file.
- Will throw an error if the host is unable to use specified algorithm.
type: str
default: sha1
choices: [ md5, sha1, sha256, sha384, sha512 ]
version_added: "2.3"
follow:
description:
- Whether to follow symlinks or junction points.
- In the case of C(path) pointing to another link, then that will
be followed until no more links are found.
type: bool
default: no
version_added: "2.8"
seealso:
- module: stat
- module: win_acl
- module: win_file
- module: win_owner
author:
- Chris Church (@cchurch)
'''
EXAMPLES = r'''
- name: Obtain information about a file
win_stat:
path: C:\foo.ini
register: file_info
- name: Obtain information about a folder
win_stat:
path: C:\bar
register: folder_info
- name: Get MD5 checksum of a file
win_stat:
path: C:\foo.ini
get_checksum: yes
checksum_algorithm: md5
register: md5_checksum
- debug:
var: md5_checksum.stat.checksum
- name: Get SHA1 checksum of file
win_stat:
path: C:\foo.ini
get_checksum: yes
register: sha1_checksum
- debug:
var: sha1_checksum.stat.checksum
- name: Get SHA256 checksum of file
win_stat:
path: C:\foo.ini
get_checksum: yes
checksum_algorithm: sha256
register: sha256_checksum
- debug:
var: sha256_checksum.stat.checksum
'''
RETURN = r'''
changed:
description: Whether anything was changed
returned: always
type: bool
sample: true
stat:
description: dictionary containing all the stat data
returned: success
type: complex
contains:
attributes:
description: Attributes of the file at path in raw form.
returned: success, path exists
type: str
sample: "Archive, Hidden"
checksum:
description: The checksum of a file based on checksum_algorithm specified.
returned: success, path exist, path is a file, get_checksum == True
checksum_algorithm specified is supported
type: str
sample: 09cb79e8fc7453c84a07f644e441fd81623b7f98
creationtime:
description: The create time of the file represented in seconds since epoch.
returned: success, path exists
type: float
sample: 1477984205.15
exists:
description: If the path exists or not.
returned: success
type: bool
sample: true
extension:
description: The extension of the file at path.
returned: success, path exists, path is a file
type: str
sample: ".ps1"
filename:
description: The name of the file (without path).
returned: success, path exists, path is a file
type: str
sample: foo.ini
hlnk_targets:
description: List of other files pointing to the same file (hard links), excludes the current file.
returned: success, path exists
type: list
sample:
- C:\temp\file.txt
- C:\Windows\update.log
isarchive:
description: If the path is ready for archiving or not.
returned: success, path exists
type: bool
sample: true
isdir:
description: If the path is a directory or not.
returned: success, path exists
type: bool
sample: true
ishidden:
description: If the path is hidden or not.
returned: success, path exists
type: bool
sample: true
isjunction:
description: If the path is a junction point or not.
returned: success, path exists
type: bool
sample: true
islnk:
description: If the path is a symbolic link or not.
returned: success, path exists
type: bool
sample: true
isreadonly:
description: If the path is read only or not.
returned: success, path exists
type: bool
sample: true
isreg:
description: If the path is a regular file.
returned: success, path exists
type: bool
sample: true
isshared:
description: If the path is shared or not.
returned: success, path exists
type: bool
sample: true
lastaccesstime:
description: The last access time of the file represented in seconds since epoch.
returned: success, path exists
type: float
sample: 1477984205.15
lastwritetime:
description: The last modification time of the file represented in seconds since epoch.
returned: success, path exists
type: float
sample: 1477984205.15
lnk_source:
description: Target of the symlink normalized for the remote filesystem.
returned: success, path exists and the path is a symbolic link or junction point
type: str
sample: C:\temp\link
lnk_target:
description: Target of the symlink. Note that relative paths remain relative.
returned: success, path exists and the path is a symbolic link or junction point
type: str
sample: ..\link
nlink:
description: Number of links to the file (hard links).
returned: success, path exists
type: int
sample: 1
owner:
description: The owner of the file.
returned: success, path exists
type: str
sample: BUILTIN\Administrators
path:
description: The full absolute path to the file.
returned: success, path exists, file exists
type: str
sample: C:\foo.ini
sharename:
description: The name of share if folder is shared.
returned: success, path exists, file is a directory and isshared == True
type: str
sample: file-share
size:
description: The size in bytes of a file or folder.
returned: success, path exists, file is not a link
type: int
sample: 1024
'''

@ -1,72 +0,0 @@
#!powershell
# Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
Function New-TempFile {
Param ([string]$path, [string]$prefix, [string]$suffix, [string]$type, [bool]$checkmode)
$temppath = $null
$curerror = $null
$attempt = 0
# Since we don't know if the file already exists, we try 5 times with a random name
do {
$attempt += 1
$randomname = [System.IO.Path]::GetRandomFileName()
$temppath = (Join-Path -Path $path -ChildPath "$prefix$randomname$suffix")
Try {
$file = New-Item -Path $temppath -ItemType $type -WhatIf:$checkmode
# Makes sure we get the full absolute path of the created temp file and not a relative or DOS 8.3 dir
if (-not $checkmode) {
$temppath = $file.FullName
} else {
# Just rely on GetFulLpath for check mode
$temppath = [System.IO.Path]::GetFullPath($temppath)
}
} Catch {
$temppath = $null
$curerror = $_
}
} until (($null -ne $temppath) -or ($attempt -ge 5))
# If it fails 5 times, something is wrong and we have to report the details
if ($null -eq $temppath) {
$module.FailJson("No random temporary file worked in $attempt attempts. Error: $($curerror.Exception.Message)", $curerror)
}
return $temppath.ToString()
}
$spec = @{
options = @{
path = @{ type='path'; default='%TEMP%'; aliases=@( 'dest' ) }
state = @{ type='str'; default='file'; choices=@( 'directory', 'file') }
prefix = @{ type='str'; default='ansible.' }
suffix = @{ type='str' }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$path = $module.Params.path
$state = $module.Params.state
$prefix = $module.Params.prefix
$suffix = $module.Params.suffix
# Expand environment variables on non-path types
if ($null -ne $prefix) {
$prefix = [System.Environment]::ExpandEnvironmentVariables($prefix)
}
if ($null -ne $suffix) {
$suffix = [System.Environment]::ExpandEnvironmentVariables($suffix)
}
$module.Result.changed = $true
$module.Result.state = $state
$module.Result.path = New-TempFile -Path $path -Prefix $prefix -Suffix $suffix -Type $state -CheckMode $module.CheckMode
$module.ExitJson()

@ -1,67 +0,0 @@
#!/usr/bin/python
# coding: utf-8 -*-
# Copyright: (c) 2017, Dag Wieers <dag@wieers.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_tempfile
version_added: "2.3"
short_description: Creates temporary files and directories
description:
- Creates temporary files and directories.
- For non-Windows targets, please use the M(tempfile) module instead.
options:
state:
description:
- Whether to create file or directory.
type: str
choices: [ directory, file ]
default: file
path:
description:
- Location where temporary file or directory should be created.
- If path is not specified default system temporary directory (%TEMP%) will be used.
type: path
default: '%TEMP%'
aliases: [ dest ]
prefix:
description:
- Prefix of file/directory name created by module.
type: str
default: ansible.
suffix:
description:
- Suffix of file/directory name created by module.
type: str
default: ''
seealso:
- module: tempfile
author:
- Dag Wieers (@dagwieers)
'''
EXAMPLES = r"""
- name: Create temporary build directory
win_tempfile:
state: directory
suffix: build
- name: Create temporary file
win_tempfile:
state: file
suffix: temp
"""
RETURN = r'''
path:
description: The absolute path to the created file or directory.
returned: success
type: str
sample: C:\Users\Administrator\AppData\Local\Temp\ansible.bMlvdk
'''

@ -1,66 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a virtual module that is entirely implemented server side
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_template
version_added: "1.9.2"
short_description: Template a file out to a remote server
options:
backup:
description:
- Determine whether a backup should be created.
- When set to C(yes), create a backup file including the timestamp information
so you can get the original file back if you somehow clobbered it incorrectly.
type: bool
default: no
version_added: '2.8'
newline_sequence:
default: '\r\n'
force:
version_added: '2.4'
notes:
- Beware fetching files from windows machines when creating templates because certain tools, such as Powershell ISE,
and regedit's export facility add a Byte Order Mark as the first character of the file, which can cause tracebacks.
- You can use the M(win_copy) module with the C(content:) option if you prefer the template inline, as part of the
playbook.
- For Linux you can use M(template) which uses '\\n' as C(newline_sequence) by default.
seealso:
- module: win_copy
- module: copy
- module: template
author:
- Jon Hawkesworth (@jhawkesworth)
extends_documentation_fragment:
- template_common
'''
EXAMPLES = r'''
- name: Create a file from a Jinja2 template
win_template:
src: /mytemplates/file.conf.j2
dest: C:\Temp\file.conf
- name: Create a Unix-style file from a Jinja2 template
win_template:
src: unix/config.conf.j2
dest: C:\share\unix\config.conf
newline_sequence: '\n'
backup: yes
'''
RETURN = r'''
backup_file:
description: Name of the backup file that was created.
returned: if backup=yes
type: str
sample: C:\Path\To\File.txt.11540.20150212-220915.bak
'''

@ -1,591 +0,0 @@
#!powershell
# Copyright: (c) 2015, Matt Davis <mdavis@rolpdog.com>
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
$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
$category_names = Get-AnsibleParam -obj $params -name "category_names" -type "list" -default @("CriticalUpdates", "SecurityUpdates", "UpdateRollups")
$log_path = Get-AnsibleParam -obj $params -name "log_path" -type "path"
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "installed" -validateset "installed", "searched", "downloaded"
$blacklist = Get-AnsibleParam -obj $params -name "blacklist" -type "list"
$whitelist = Get-AnsibleParam -obj $params -name "whitelist" -type "list"
$server_selection = Get-AnsibleParam -obj $params -name "server_selection" -type "string" -default "default" -validateset "default", "managed_server", "windows_update"
# For backwards compatibility
Function Get-CategoryMapping ($category_name) {
switch -exact ($category_name) {
"CriticalUpdates" {return "Critical Updates"}
"DefinitionUpdates" {return "Definition Updates"}
"DeveloperKits" {return "Developer Kits"}
"FeaturePacks" {return "Feature Packs"}
"SecurityUpdates" {return "Security Updates"}
"ServicePacks" {return "Service Packs"}
"UpdateRollups" {return "Update Rollups"}
default {return $category_name}
}
}
$category_names = $category_names | ForEach-Object { Get-CategoryMapping -category_name $_ }
$common_functions = {
Function Write-DebugLog($msg) {
$date_str = Get-Date -Format u
$msg = "$date_str $msg"
Write-Debug -Message $msg
if ($null -ne $log_path -and (-not $check_mode)) {
Add-Content -Path $log_path -Value $msg
}
}
}
$update_script_block = {
Param(
[hashtable]$arguments
)
$ErrorActionPreference = "Stop"
$DebugPreference = "Continue"
Function Start-Updates {
Param(
$category_names,
$log_path,
$state,
$blacklist,
$whitelist,
$server_selection
)
$result = @{
changed = $false
updates = @{}
filtered_updates = @{}
}
Write-DebugLog -msg "Creating Windows Update session..."
try {
$session = New-Object -ComObject Microsoft.Update.Session
} catch {
$result.failed = $true
$result.msg = "Failed to create Microsoft.Update.Session COM object: $($_.Exception.Message)"
return $result
}
Write-DebugLog -msg "Create Windows Update searcher..."
try {
$searcher = $session.CreateUpdateSearcher()
} catch {
$result.failed = $true
$result.msg = "Failed to create Windows Update search from session: $($_.Exception.Message)"
return $result
}
Write-DebugLog -msg "Setting the Windows Update Agent source catalog..."
Write-DebugLog -msg "Requested search source is '$($server_selection)'"
try {
$server_selection_value = switch ($server_selection) {
"default" { 0 ; break }
"managed_server" { 1 ; break }
"windows_update" { 2 ; break }
}
$searcher.serverselection = $server_selection_value
Write-DebugLog -msg "Search source set to '$($server_selection)' (ServerSelection = $($server_selection_value))"
}
catch {
$result.failed = $true
$result.msg = "Failed to set Windows Update Agent search source: $($_.Exception.Message)"
return $result
}
Write-DebugLog -msg "Searching for updates to install"
try {
$search_result = $searcher.Search("IsInstalled = 0")
} catch {
$result.failed = $true
$result.msg = "Failed to search for updates: $($_.Exception.Message)"
return $result
}
Write-DebugLog -msg "Found $($search_result.Updates.Count) updates"
Write-DebugLog -msg "Creating update collection..."
try {
$updates_to_install = New-Object -ComObject Microsoft.Update.UpdateColl
} catch {
$result.failed = $true
$result.msg = "Failed to create update collection object: $($_.Exception.Message)"
return $result
}
foreach ($update in $search_result.Updates) {
$update_info = @{
title = $update.Title
# TODO: pluck the first KB out (since most have just one)?
kb = $update.KBArticleIDs
id = $update.Identity.UpdateId
installed = $false
categories = @($update.Categories | ForEach-Object { $_.Name })
}
# validate update again blacklist/whitelist/post_category_names/hidden
$whitelist_match = $false
foreach ($whitelist_entry in $whitelist) {
if ($update_info.title -imatch $whitelist_entry) {
$whitelist_match = $true
break
}
foreach ($kb in $update_info.kb) {
if ("KB$kb" -imatch $whitelist_entry) {
$whitelist_match = $true
break
}
}
}
if ($whitelist.Length -gt 0 -and -not $whitelist_match) {
Write-DebugLog -msg "Skipping update $($update_info.id) - $($update_info.title) as it was not found in the whitelist"
$update_info.filtered_reason = "whitelist"
$result.filtered_updates[$update_info.id] = $update_info
continue
}
$blacklist_match = $false
foreach ($blacklist_entry in $blacklist) {
if ($update_info.title -imatch $blacklist_entry) {
$blacklist_match = $true
break
}
foreach ($kb in $update_info.kb) {
if ("KB$kb" -imatch $blacklist_entry) {
$blacklist_match = $true
break
}
}
}
if ($blacklist_match) {
Write-DebugLog -msg "Skipping update $($update_info.id) - $($update_info.title) as it was found in the blacklist"
$update_info.filtered_reason = "blacklist"
$result.filtered_updates[$update_info.id] = $update_info
continue
}
if ($update.IsHidden) {
Write-DebugLog -msg "Skipping update $($update_info.title) as it was hidden"
$update_info.filtered_reason = "skip_hidden"
$result.filtered_updates[$update_info.id] = $update_info
continue
}
$category_match = $false
foreach ($match_cat in $category_names) {
if ($update_info.categories -ieq $match_cat) {
$category_match = $true
break
}
}
if ($category_names.Length -gt 0 -and -not $category_match) {
Write-DebugLog -msg "Skipping update $($update_info.id) - $($update_info.title) as it was not found in the category names filter"
$update_info.filtered_reason = "category_names"
$result.filtered_updates[$update_info.id] = $update_info
continue
}
if (-not $update.EulaAccepted) {
Write-DebugLog -msg "Accepting EULA for $($update_info.id)"
try {
$update.AcceptEula()
} catch {
$result.failed = $true
$result.msg = "Failed to accept EULA for update $($update_info.id) - $($update_info.title)"
return $result
}
}
Write-DebugLog -msg "Adding update $($update_info.id) - $($update_info.title)"
$updates_to_install.Add($update) > $null
$result.updates[$update_info.id] = $update_info
}
Write-DebugLog -msg "Calculating pre-install reboot requirement..."
# calculate this early for check mode, and to see if we should allow updates to continue
$result.reboot_required = (New-Object -ComObject Microsoft.Update.SystemInfo).RebootRequired
$result.found_update_count = $updates_to_install.Count
$result.installed_update_count = 0
# Early exit of check mode/state=searched as it cannot do more after this
if ($check_mode -or $state -eq "searched") {
Write-DebugLog -msg "Check mode: exiting..."
Write-DebugLog -msg "Return value:`r`n$(ConvertTo-Json -InputObject $result -Depth 99)"
if ($updates_to_install.Count -gt 0 -and ($state -ne "searched")) {
$result.changed = $true
}
return $result
}
if ($updates_to_install.Count -gt 0) {
if ($result.reboot_required) {
Write-DebugLog -msg "FATAL: A reboot is required before more updates can be installed"
$result.failed = $true
$result.msg = "A reboot is required before more updates can be installed"
return $result
}
Write-DebugLog -msg "No reboot is pending..."
} else {
# no updates to install exit here
return $result
}
Write-DebugLog -msg "Downloading updates..."
$update_index = 1
foreach ($update in $updates_to_install) {
$update_number = "($update_index of $($updates_to_install.Count))"
if ($update.IsDownloaded) {
Write-DebugLog -msg "Update $update_number $($update.Identity.UpdateId) already downloaded, skipping..."
$update_index++
continue
}
Write-DebugLog -msg "Creating downloader object..."
try {
$dl = $session.CreateUpdateDownloader()
} catch {
$result.failed = $true
$result.msg = "Failed to create downloader object: $($_.Exception.Message)"
return $result
}
Write-DebugLog -msg "Creating download collection..."
try {
$dl.Updates = New-Object -ComObject Microsoft.Update.UpdateColl
} catch {
$result.failed = $true
$result.msg = "Failed to create download collection object: $($_.Exception.Message)"
return $result
}
Write-DebugLog -msg "Adding update $update_number $($update.Identity.UpdateId)"
$dl.Updates.Add($update) > $null
Write-DebugLog -msg "Downloading $update_number $($update.Identity.UpdateId)"
try {
$download_result = $dl.Download()
} catch {
$result.failed = $true
$result.msg = "Failed to download update $update_number $($update.Identity.UpdateId) - $($update.Title): $($_.Exception.Message)"
return $result
}
Write-DebugLog -msg "Download result code for $update_number $($update.Identity.UpdateId) = $($download_result.ResultCode)"
# FUTURE: configurable download retry
if ($download_result.ResultCode -ne 2) { # OperationResultCode orcSucceeded
$result.failed = $true
$result.msg = "Failed to download update $update_number $($update.Identity.UpdateId) - $($update.Title): Download Result $($download_result.ResultCode)"
return $result
}
$result.changed = $true
$update_index++
}
# Early exit for download-only
if ($state -eq "downloaded") {
Write-DebugLog -msg "Downloaded $($updates_to_install.Count) updates..."
$result.failed = $false
$result.msg = "Downloaded $($updates_to_install.Count) updates"
return $result
}
Write-DebugLog -msg "Installing updates..."
# install as a batch so the reboot manager will suppress intermediate reboots
Write-DebugLog -msg "Creating installer object..."
try {
$installer = $session.CreateUpdateInstaller()
} catch {
$result.failed = $true
$result.msg = "Failed to create Update Installer object: $($_.Exception.Message)"
return $result
}
Write-DebugLog -msg "Creating install collection..."
try {
$installer.Updates = New-Object -ComObject Microsoft.Update.UpdateColl
} catch {
$result.failed = $true
$result.msg = "Failed to create Update Collection object: $($_.Exception.Message)"
return $result
}
foreach ($update in $updates_to_install) {
Write-DebugLog -msg "Adding update $($update.Identity.UpdateID)"
$installer.Updates.Add($update) > $null
}
# FUTURE: use BeginInstall w/ progress reporting so we can at least log intermediate install results
try {
$install_result = $installer.Install()
} catch {
$result.failed = $true
$result.msg = "Failed to install update from Update Collection: $($_.Exception.Message)"
return $result
}
$update_success_count = 0
$update_fail_count = 0
# WU result API requires us to index in to get the install results
$update_index = 0
foreach ($update in $updates_to_install) {
$update_number = "($($update_index + 1) of $($updates_to_install.Count))"
try {
$update_result = $install_result.GetUpdateResult($update_index)
} catch {
$result.failed = $true
$result.msg = "Failed to get update result for update $update_number $($update.Identity.UpdateID) - $($update.Title): $($_.Exception.Message)"
return $result
}
$update_resultcode = $update_result.ResultCode
$update_hresult = $update_result.HResult
$update_index++
$update_dict = $result.updates[$update.Identity.UpdateID]
if ($update_resultcode -eq 2) { # OperationResultCode orcSucceeded
$update_success_count++
$update_dict.installed = $true
Write-DebugLog -msg "Update $update_number $($update.Identity.UpdateID) succeeded"
} else {
$update_fail_count++
$update_dict.installed = $false
$update_dict.failed = $true
$update_dict.failure_hresult_code = $update_hresult
Write-DebugLog -msg "Update $update_number $($update.Identity.UpdateID) failed, resultcode: $update_resultcode, hresult: $update_hresult"
}
}
Write-DebugLog -msg "Performing post-install reboot requirement check..."
$result.reboot_required = (New-Object -ComObject Microsoft.Update.SystemInfo).RebootRequired
$result.installed_update_count = $update_success_count
$result.failed_update_count = $update_fail_count
if ($updates_success_count -gt 0) {
$result.changed = $true
}
if ($update_fail_count -gt 0) {
$result.failed = $true
$result.msg = "Failed to install one or more updates"
return $result
}
Write-DebugLog -msg "Return value:`r`n$(ConvertTo-Json -InputObject $result -Depth 99)"
return $result
}
$check_mode = $arguments.check_mode
try {
return @{
job_output = Start-Updates @arguments
}
} catch {
Write-DebugLog -msg "Fatal exception: $($_.Exception.Message) at $($_.ScriptStackTrace)"
return @{
job_output = @{
failed = $true
msg = $_.Exception.Message
location = $_.ScriptStackTrace
}
}
}
}
Function Start-Natively($common_functions, $script) {
$runspace_pool = [RunspaceFactory]::CreateRunspacePool()
$runspace_pool.Open()
try {
$ps_pipeline = [PowerShell]::Create()
$ps_pipeline.RunspacePool = $runspace_pool
# add the common script functions
$ps_pipeline.AddScript($common_functions) > $null
# add the update script block and required parameters
$ps_pipeline.AddStatement().AddScript($script) > $null
$ps_pipeline.AddParameter("arguments", @{
category_names = $category_names
log_path = $log_path
state = $state
blacklist = $blacklist
whitelist = $whitelist
check_mode = $check_mode
server_selection = $server_selection
}) > $null
$output = $ps_pipeline.Invoke()
} finally {
$runspace_pool.Close()
}
$result = $output[0].job_output
if ($ps_pipeline.HadErrors) {
$result.failed = $true
# if the msg wasn't set, then add a generic error to at least tell the user something
if (-not ($result.ContainsKey("msg"))) {
$result.msg = "Unknown failure when executing native update script block"
$result.errors = $ps_pipeline.Streams.Error
}
}
Write-DebugLog -msg "Native job completed with output: $($result | Out-String -Width 300)"
return ,$result
}
Function Remove-ScheduledJob($name) {
$scheduled_job = Get-ScheduledJob -Name $name -ErrorAction SilentlyContinue
if ($null -ne $scheduled_job) {
Write-DebugLog -msg "Scheduled Job $name exists, ensuring it is not running..."
$scheduler = New-Object -ComObject Schedule.Service
Write-DebugLog -msg "Connecting to scheduler service..."
$scheduler.Connect()
Write-DebugLog -msg "Getting running tasks named $name"
$running_tasks = @($scheduler.GetRunningTasks(0) | Where-Object { $_.Name -eq $name })
foreach ($task_to_stop in $running_tasks) {
Write-DebugLog -msg "Stopping running task $($task_to_stop.InstanceGuid)..."
$task_to_stop.Stop()
}
<# FUTURE: add a global waithandle for this to release any other waiters. Wait-Job
and/or polling will block forever, since the killed job object in the parent
session doesn't know it's been killed :( #>
Unregister-ScheduledJob -Name $name
}
}
Function Start-AsScheduledTask($common_functions, $script) {
$job_name = "ansible-win-updates"
Remove-ScheduledJob -name $job_name
$job_args = @{
ScriptBlock = $script
Name = $job_name
ArgumentList = @(
@{
category_names = $category_names
log_path = $log_path
state = $state
blacklist = $blacklist
whitelist = $whitelist
check_mode = $check_mode
server_selection = $server_selection
}
)
ErrorAction = "Stop"
ScheduledJobOption = @{ RunElevated=$True; StartIfOnBatteries=$True; StopIfGoingOnBatteries=$False }
InitializationScript = $common_functions
}
Write-DebugLog -msg "Registering scheduled job with args $($job_args | Out-String -Width 300)"
$scheduled_job = Register-ScheduledJob @job_args
# RunAsTask isn't available in PS3 - fall back to a 2s future trigger
if ($scheduled_job | Get-Member -Name RunAsTask) {
Write-DebugLog -msg "Starting scheduled job (PS4+ method)"
$scheduled_job.RunAsTask()
} else {
Write-DebugLog -msg "Starting scheduled job (PS3 method)"
Add-JobTrigger -InputObject $scheduled_job -trigger $(New-JobTrigger -Once -At $(Get-Date).AddSeconds(2))
}
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$job = $null
Write-DebugLog -msg "Waiting for job completion..."
# Wait-Job can fail for a few seconds until the scheduled task starts - poll for it...
while ($null -eq $job) {
Start-Sleep -Milliseconds 100
if ($sw.ElapsedMilliseconds -ge 30000) { # tasks scheduled right after boot on 2008R2 can take awhile to start...
Fail-Json -msg "Timed out waiting for scheduled task to start"
}
# FUTURE: configurable timeout so we don't block forever?
# FUTURE: add a global WaitHandle in case another instance kills our job, so we don't block forever
$job = Wait-Job -Name $scheduled_job.Name -ErrorAction SilentlyContinue
}
$sw = [System.Diagnostics.Stopwatch]::StartNew()
# NB: output from scheduled jobs is delayed after completion (including the sub-objects after the primary Output object is available)
while (($null -eq $job.Output -or -not ($job.Output | Get-Member -Name Key -ErrorAction Ignore) -or -not $job.Output.Key.Contains("job_output")) -and $sw.ElapsedMilliseconds -lt 15000) {
Write-DebugLog -msg "Waiting for job output to populate..."
Start-Sleep -Milliseconds 500
}
# NB: fallthru on both timeout and success
$ret = @{
ErrorOutput = $job.Error
WarningOutput = $job.Warning
VerboseOutput = $job.Verbose
DebugOutput = $job.Debug
}
if ($null -eq $job.Output -or -not $job.Output.Keys.Contains('job_output')) {
$ret.Output = @{failed = $true; msg = "job output was lost"}
} else {
$ret.Output = $job.Output.job_output # sub-object returned, can only be accessed as a property for some reason
}
try { # this shouldn't be fatal, but can fail with both Powershell errors and COM Exceptions, hence the dual error-handling...
Unregister-ScheduledJob -Name $job_name -Force -ErrorAction Continue
} catch {
Write-DebugLog "Error unregistering job after execution: $($_.Exception.ToString()) $($_.ScriptStackTrace)"
}
Write-DebugLog -msg "Scheduled job completed with output: $($re.Output | Out-String -Width 300)"
return $ret.Output
}
# source the common code into the current scope so we can call it
. $common_functions
<# Most of the Windows Update Agent API will not run under a remote token,
which a remote WinRM session always has. Using become can bypass this
limitation but it is not always an option with older hosts. win_updates checks
if WUA is available in the current logon process and does either of the below;
* If become is used then it will run the windows update process natively
without any of the scheduled task hackery
* If become is not used then it will run the windows update process under
a scheduled job.
#>
try {
(New-Object -ComObject Microsoft.Update.Session).CreateUpdateInstaller().IsBusy > $null
$wua_available = $true
} catch {
$wua_available = $false
}
if ($wua_available) {
Write-DebugLog -msg "WUA is available in current logon process, running natively"
$result = Start-Natively -common_functions $common_functions -script $update_script_block
} else {
Write-DebugLog -msg "WUA is not available in current logon process, running with scheduled task"
$result = Start-AsScheduledTask -common_functions $common_functions -script $update_script_block
}
Exit-Json -obj $result

@ -1,273 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Matt Davis <mdavis_ansible@rolpdog.com>
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_updates
version_added: "2.0"
short_description: Download and install Windows updates
description:
- Searches, downloads, and installs Windows updates synchronously by automating the Windows Update client.
options:
blacklist:
description:
- A list of update titles or KB numbers that can be used to specify
which updates are to be excluded from installation.
- If an available update does match one of the entries, then it is
skipped and not installed.
- Each entry can either be the KB article or Update title as a regex
according to the PowerShell regex rules.
type: list
version_added: '2.5'
category_names:
description:
- A scalar or list of categories to install updates from. To get the list
of categories, run the module with C(state=searched). The category must
be the full category string, but is case insensitive.
- Some possible categories are Application, Connectors, Critical Updates,
Definition Updates, Developer Kits, Feature Packs, Guidance, Security
Updates, Service Packs, Tools, Update Rollups and Updates.
type: list
default: [ CriticalUpdates, SecurityUpdates, UpdateRollups ]
reboot:
description:
- Ansible will automatically reboot the remote host if it is required
and continue to install updates after the reboot.
- This can be used instead of using a M(win_reboot) task after this one
and ensures all updates for that category is installed in one go.
- Async does not work when C(reboot=yes).
type: bool
default: no
version_added: '2.5'
reboot_timeout:
description:
- The time in seconds to wait until the host is back online from a
reboot.
- This is only used if C(reboot=yes) and a reboot is required.
default: 1200
version_added: '2.5'
server_selection:
description:
- Defines the Windows Update source catalog.
- C(default) Use the default search source. For many systems default is
set to the Microsoft Windows Update catalog. Systems participating in
Windows Server Update Services (WSUS), Systems Center Configuration
Manager (SCCM), or similar corporate update server environments may
default to those managed update sources instead of the Windows Update
catalog.
- C(managed_server) Use a managed server catalog. For environments
utilizing Windows Server Update Services (WSUS), Systems Center
Configuration Manager (SCCM), or similar corporate update servers, this
option selects the defined corporate update source.
- C(windows_update) Use the Microsoft Windows Update catalog.
type: str
choices: [ default, managed_server, windows_update ]
default: default
version_added: '2.8'
state:
description:
- Controls whether found updates are downloaded or installed or listed
- This module also supports Ansible check mode, which has the same effect as setting state=searched
type: str
choices: [ installed, searched, downloaded ]
default: installed
log_path:
description:
- If set, C(win_updates) will append update progress to the specified file. The directory must already exist.
type: path
whitelist:
description:
- A list of update titles or KB numbers that can be used to specify
which updates are to be searched or installed.
- If an available update does not match one of the entries, then it
is skipped and not installed.
- Each entry can either be the KB article or Update title as a regex
according to the PowerShell regex rules.
- The whitelist is only validated on updates that were found based on
I(category_names). It will not force the module to install an update
if it was not in the category specified.
type: list
version_added: '2.5'
use_scheduled_task:
description:
- Will not auto elevate the remote process with I(become) and use a
scheduled task instead.
- Set this to C(yes) when using this module with async on Server 2008,
2008 R2, or Windows 7, or on Server 2008 that is not authenticated
with basic or credssp.
- Can also be set to C(yes) on newer hosts where become does not work
due to further privilege restrictions from the OS defaults.
type: bool
default: no
version_added: '2.6'
notes:
- C(win_updates) must be run by a user with membership in the local Administrators group.
- C(win_updates) will use the default update service configured for the machine (Windows Update, Microsoft Update, WSUS, etc).
- C(win_updates) will I(become) SYSTEM using I(runas) unless C(use_scheduled_task) is C(yes)
- By default C(win_updates) does not manage reboots, but will signal when a
reboot is required with the I(reboot_required) return value, as of Ansible v2.5
C(reboot) can be used to reboot the host if required in the one task.
- C(win_updates) can take a significant amount of time to complete (hours, in some cases).
Performance depends on many factors, including OS version, number of updates, system load, and update server load.
- Beware that just after C(win_updates) reboots the system, the Windows system may not have settled yet
and some base services could be in limbo. This can result in unexpected behavior.
Check the examples for ways to mitigate this.
- More information about PowerShell and how it handles RegEx strings can be
found at U(https://technet.microsoft.com/en-us/library/2007.11.powershell.aspx).
seealso:
- module: win_chocolatey
- module: win_feature
- module: win_hotfix
- module: win_package
author:
- Matt Davis (@nitzmahone)
'''
EXAMPLES = r'''
- name: Install all security, critical, and rollup updates without a scheduled task
win_updates:
category_names:
- SecurityUpdates
- CriticalUpdates
- UpdateRollups
- name: Install only security updates as a scheduled task for Server 2008
win_updates:
category_names: SecurityUpdates
use_scheduled_task: yes
- name: Search-only, return list of found updates (if any), log to C:\ansible_wu.txt
win_updates:
category_names: SecurityUpdates
state: searched
log_path: C:\ansible_wu.txt
- name: Install all security updates with automatic reboots
win_updates:
category_names:
- SecurityUpdates
reboot: yes
- name: Install only particular updates based on the KB numbers
win_updates:
category_name:
- SecurityUpdates
whitelist:
- KB4056892
- KB4073117
- name: Exclude updates based on the update title
win_updates:
category_name:
- SecurityUpdates
- CriticalUpdates
blacklist:
- Windows Malicious Software Removal Tool for Windows
- \d{4}-\d{2} Cumulative Update for Windows Server 2016
# One way to ensure the system is reliable just after a reboot, is to set WinRM to a delayed startup
- name: Ensure WinRM starts when the system has settled and is ready to work reliably
win_service:
name: WinRM
start_mode: delayed
# Optionally, you can increase the reboot_timeout to survive long updates during reboot
- name: Ensure we wait long enough for the updates to be applied during reboot
win_updates:
reboot: yes
reboot_timeout: 3600
# Search and download Windows updates
- name: Search and download Windows updates without installing them
win_updates:
state: downloaded
'''
RETURN = r'''
reboot_required:
description: True when the target server requires a reboot to complete updates (no further updates can be installed until after a reboot).
returned: success
type: bool
sample: true
updates:
description: List of updates that were found/installed.
returned: success
type: complex
sample:
contains:
title:
description: Display name.
returned: always
type: str
sample: "Security Update for Windows Server 2012 R2 (KB3004365)"
kb:
description: A list of KB article IDs that apply to the update.
returned: always
type: list
elements: str
sample: [ '3004365' ]
id:
description: Internal Windows Update GUID.
returned: always
type: str
sample: "fb95c1c8-de23-4089-ae29-fd3351d55421"
installed:
description: Was the update successfully installed.
returned: always
type: bool
sample: true
categories:
description: A list of category strings for this update.
returned: always
type: list
elements: str
sample: [ 'Critical Updates', 'Windows Server 2012 R2' ]
failure_hresult_code:
description: The HRESULT code from a failed update.
returned: on install failure
type: bool
sample: 2147942402
filtered_updates:
description: List of updates that were found but were filtered based on
I(blacklist), I(whitelist) or I(category_names). The return value is in
the same form as I(updates), along with I(filtered_reason).
returned: success
type: complex
sample: see the updates return value
contains:
filtered_reason:
description: The reason why this update was filtered.
returned: always
type: str
sample: 'skip_hidden'
found_update_count:
description: The number of updates found needing to be applied.
returned: success
type: int
sample: 3
installed_update_count:
description: The number of updates successfully installed or downloaded.
returned: success
type: int
sample: 2
failed_update_count:
description: The number of updates that failed to install.
returned: always
type: int
sample: 0
'''

@ -1,185 +0,0 @@
#!powershell
# Copyright: (c) 2015, Corwin Brown <corwin@corwinbrown.com>
# Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.CamelConversion
#Requires -Module Ansible.ModuleUtils.FileUtil
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.WebRequest
$spec = @{
options = @{
url = @{ type = "str"; required = $true }
content_type = @{ type = "str" }
body = @{ type = "raw" }
dest = @{ type = "path" }
creates = @{ type = "path" }
method = @{ default = "GET" }
removes = @{ type = "path" }
return_content = @{ type = "bool"; default = $false }
status_code = @{ type = "list"; elements = "int"; default = @(200) }
# Defined for the alias backwards compatibility, remove once aliases are removed
url_username = @{
aliases = @("user", "username")
deprecated_aliases = @(
@{ name = "user"; version = "2.14" },
@{ name = "username"; version = "2.14" }
)
}
url_password = @{
aliases = @("password")
deprecated_aliases = @(
@{ name = "password"; version = "2.14" }
)
}
}
supports_check_mode = $true
}
$spec = Merge-WebRequestSpec -ModuleSpec $spec
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$url = $module.Params.url
$method = $module.Params.method.ToUpper()
$content_type = $module.Params.content_type
$body = $module.Params.body
$dest = $module.Params.dest
$creates = $module.Params.creates
$removes = $module.Params.removes
$return_content = $module.Params.return_content
$status_code = $module.Params.status_code
$JSON_CANDIDATES = @('text', 'json', 'javascript')
$module.Result.elapsed = 0
$module.Result.url = $url
if (-not ($method -cmatch '^[A-Z]+$')) {
$module.FailJson("Parameter 'method' needs to be a single word in uppercase, like GET or POST.")
}
if ($creates -and (Test-AnsiblePath -Path $creates)) {
$module.Result.skipped = $true
$module.Result.msg = "The 'creates' file or directory ($creates) already exists."
$module.ExitJson()
}
if ($removes -and -not (Test-AnsiblePath -Path $removes)) {
$module.Result.skipped = $true
$module.Result.msg = "The 'removes' file or directory ($removes) does not exist."
$module.ExitJson()
}
$client = Get-AnsibleWebRequest -Uri $url -Module $module
if ($null -ne $content_type) {
$client.ContentType = $content_type
}
$response_script = {
param($Response, $Stream)
ForEach ($prop in $Response.PSObject.Properties) {
$result_key = Convert-StringToSnakeCase -string $prop.Name
$prop_value = $prop.Value
# convert and DateTime values to ISO 8601 standard
if ($prop_value -is [System.DateTime]) {
$prop_value = $prop_value.ToString("o", [System.Globalization.CultureInfo]::InvariantCulture)
}
$module.Result.$result_key = $prop_value
}
# manually get the headers as not all of them are in the response properties
foreach ($header_key in $Response.Headers.GetEnumerator()) {
$header_value = $Response.Headers[$header_key]
$header_key = $header_key.Replace("-", "") # replace - with _ for snake conversion
$header_key = Convert-StringToSnakeCase -string $header_key
$module.Result.$header_key = $header_value
}
# we only care about the return body if we need to return the content or create a file
if ($return_content -or $dest) {
# copy to a MemoryStream so we can read it multiple times
$memory_st = New-Object -TypeName System.IO.MemoryStream
try {
$Stream.CopyTo($memory_st)
if ($return_content) {
$memory_st.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
$content_bytes = $memory_st.ToArray()
$module.Result.content = [System.Text.Encoding]::UTF8.GetString($content_bytes)
if ($module.Result.ContainsKey("content_type") -and $module.Result.content_type -Match ($JSON_CANDIDATES -join '|')) {
try {
$module.Result.json = ([Ansible.Basic.AnsibleModule]::FromJson($module.Result.content))
} catch [System.ArgumentException] {
# Simply continue, since 'text' might be anything
}
}
}
if ($dest) {
$memory_st.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
$changed = $true
if (Test-AnsiblePath -Path $dest) {
$actual_checksum = Get-FileChecksum -path $dest -algorithm "sha1"
$sp = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider
$content_checksum = [System.BitConverter]::ToString($sp.ComputeHash($memory_st)).Replace("-", "").ToLower()
if ($actual_checksum -eq $content_checksum) {
$changed = $false
}
}
$module.Result.changed = $changed
if ($changed -and (-not $module.CheckMode)) {
$memory_st.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
$file_stream = [System.IO.File]::Create($dest)
try {
$memory_st.CopyTo($file_stream)
} finally {
$file_stream.Flush()
$file_stream.Close()
}
}
}
} finally {
$memory_st.Close()
}
}
if ($status_code -notcontains $Response.StatusCode) {
$module.FailJson("Status code of request '$([int]$Response.StatusCode)' is not in list of valid status codes $status_code : $($Response.StatusCode)'.")
}
}
$body_st = $null
if ($null -ne $body) {
if ($body -is [System.Collections.IDictionary] -or $body -is [System.Collections.IList]) {
$body_string = ConvertTo-Json -InputObject $body -Compress
} elseif ($body -isnot [String]) {
$body_string = $body.ToString()
} else {
$body_string = $body
}
$buffer = [System.Text.Encoding]::UTF8.GetBytes($body_string)
$body_st = New-Object -TypeName System.IO.MemoryStream -ArgumentList @(,$buffer)
}
try {
Invoke-WithWebRequest -Module $module -Request $client -Script $response_script -Body $body_st -IgnoreBadResponse
} catch {
$module.FailJson("Unhandled exception occurred when sending web request. Exception: $($_.Exception.Message)", $_)
} finally {
if ($null -ne $body_st) {
$body_st.Dispose()
}
}
$module.ExitJson()

@ -1,180 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Corwin Brown <corwin@corwinbrown.com>
# Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_uri
version_added: '2.1'
short_description: Interacts with webservices
description:
- Interacts with FTP, HTTP and HTTPS web services.
- Supports Digest, Basic and WSSE HTTP authentication mechanisms.
- For non-Windows targets, use the M(uri) module instead.
options:
url:
description:
- Supports FTP, HTTP or HTTPS URLs in the form of (ftp|http|https)://host.domain:port/path.
type: str
required: yes
method:
description:
- The HTTP Method of the request or response.
type: str
default: GET
content_type:
description:
- Sets the "Content-Type" header.
type: str
body:
description:
- The body of the HTTP request/response to the web service.
type: raw
dest:
description:
- Output the response body to a file.
type: path
version_added: '2.3'
creates:
description:
- A filename, when it already exists, this step will be skipped.
type: path
version_added: '2.4'
removes:
description:
- A filename, when it does not exist, this step will be skipped.
type: path
version_added: '2.4'
return_content:
description:
- Whether or not to return the body of the response as a "content" key in
the dictionary result. If the reported Content-type is
"application/json", then the JSON is additionally loaded into a key
called C(json) in the dictionary results.
type: bool
default: no
version_added: '2.4'
status_code:
description:
- A valid, numeric, HTTP status code that signifies success of the request.
- Can also be comma separated list of status codes.
type: list
elements: int
default: [ 200 ]
version_added: '2.4'
url_username:
description:
- The username to use for authentication.
- Was originally called I(user) but was changed to I(url_username) in
Ansible 2.9.
- The aliases I(user) and I(username) are deprecated and will be removed in
Ansible 2.14.
aliases:
- user
- username
version_added: "2.4"
url_password:
description:
- The password for I(url_username).
- Was originally called I(password) but was changed to I(url_password) in
Ansible 2.9.
- The alias I(password) is deprecated and will be removed in Ansible 2.14.
aliases:
- password
version_added: "2.4"
follow_redirects:
version_added: "2.4"
maximum_redirection:
version_added: "2.4"
client_cert:
version_added: "2.4"
client_cert_password:
version_added: "2.5"
use_proxy:
version_added: "2.9"
proxy_url:
version_added: "2.9"
proxy_username:
version_added: "2.9"
proxy_password:
version_added: "2.9"
extends_documentation_fragment:
- url_windows
seealso:
- module: uri
- module: win_get_url
author:
- Corwin Brown (@blakfeld)
- Dag Wieers (@dagwieers)
'''
EXAMPLES = r'''
- name: Perform a GET and Store Output
win_uri:
url: http://example.com/endpoint
register: http_output
# Set a HOST header to hit an internal webserver:
- name: Hit a Specific Host on the Server
win_uri:
url: http://example.com/
method: GET
headers:
host: www.somesite.com
- name: Perform a HEAD on an Endpoint
win_uri:
url: http://www.example.com/
method: HEAD
- name: POST a Body to an Endpoint
win_uri:
url: http://www.somesite.com/
method: POST
body: "{ 'some': 'json' }"
'''
RETURN = r'''
elapsed:
description: The number of seconds that elapsed while performing the download.
returned: always
type: float
sample: 23.2
url:
description: The Target URL.
returned: always
type: str
sample: https://www.ansible.com
status_code:
description: The HTTP Status Code of the response.
returned: success
type: int
sample: 200
status_description:
description: A summary of the status.
returned: success
type: str
sample: OK
content:
description: The raw content of the HTTP response.
returned: success and return_content is True
type: str
sample: '{"foo": "bar"}'
content_length:
description: The byte size of the response.
returned: success
type: int
sample: 54447
json:
description: The json structure returned under content as a dictionary.
returned: success and Content-Type is "application/json" or "application/javascript" and return_content is True
type: dict
sample: {"this-is-dependent": "on the actual return content"}
'''

@ -1,273 +0,0 @@
#!powershell
# Copyright: (c) 2014, Paul Durivage <paul.durivage@rackspace.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.AccessToken
#Requires -Module Ansible.ModuleUtils.Legacy
########
$ADS_UF_PASSWD_CANT_CHANGE = 64
$ADS_UF_DONT_EXPIRE_PASSWD = 65536
$adsi = [ADSI]"WinNT://$env:COMPUTERNAME"
function Get-User($user) {
$adsi.Children | Where-Object {$_.SchemaClassName -eq 'user' -and $_.Name -eq $user }
return
}
function Get-UserFlag($user, $flag) {
If ($user.UserFlags[0] -band $flag) {
$true
}
Else {
$false
}
}
function Set-UserFlag($user, $flag) {
$user.UserFlags = ($user.UserFlags[0] -BOR $flag)
}
function Clear-UserFlag($user, $flag) {
$user.UserFlags = ($user.UserFlags[0] -BXOR $flag)
}
function Get-Group($grp) {
$adsi.Children | Where-Object { $_.SchemaClassName -eq 'Group' -and $_.Name -eq $grp }
return
}
Function Test-LocalCredential {
param([String]$Username, [String]$Password)
try {
$handle = [Ansible.AccessToken.TokenUtil]::LogonUser($Username, $null, $Password, "Network", "Default")
$handle.Dispose()
$valid_credentials = $true
} catch [Ansible.AccessToken.Win32Exception] {
# following errors indicate the creds are correct but the user was
# unable to log on for other reasons, which we don't care about
$success_codes = @(
0x0000052F, # ERROR_ACCOUNT_RESTRICTION
0x00000530, # ERROR_INVALID_LOGON_HOURS
0x00000531, # ERROR_INVALID_WORKSTATION
0x00000569 # ERROR_LOGON_TYPE_GRANTED
)
if ($_.Exception.NativeErrorCode -eq 0x0000052E) {
# ERROR_LOGON_FAILURE - the user or pass was incorrect
$valid_credentials = $false
} elseif ($_.Exception.NativeErrorCode -in $success_codes) {
$valid_credentials = $true
} else {
# an unknown failure, reraise exception
throw $_
}
}
return $valid_credentials
}
########
$params = Parse-Args $args;
$result = @{
changed = $false
};
$username = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$fullname = Get-AnsibleParam -obj $params -name "fullname" -type "str"
$description = Get-AnsibleParam -obj $params -name "description" -type "str"
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","query"
$update_password = Get-AnsibleParam -obj $params -name "update_password" -type "str" -default "always" -validateset "always","on_create"
$password_expired = Get-AnsibleParam -obj $params -name "password_expired" -type "bool"
$password_never_expires = Get-AnsibleParam -obj $params -name "password_never_expires" -type "bool"
$user_cannot_change_password = Get-AnsibleParam -obj $params -name "user_cannot_change_password" -type "bool"
$account_disabled = Get-AnsibleParam -obj $params -name "account_disabled" -type "bool"
$account_locked = Get-AnsibleParam -obj $params -name "account_locked" -type "bool"
$groups = Get-AnsibleParam -obj $params -name "groups"
$groups_action = Get-AnsibleParam -obj $params -name "groups_action" -type "str" -default "replace" -validateset "add","remove","replace"
If ($null -ne $account_locked -and $account_locked) {
Fail-Json $result "account_locked must be set to 'no' if provided"
}
If ($null -ne $groups) {
If ($groups -is [System.String]) {
[string[]]$groups = $groups.Split(",")
}
ElseIf ($groups -isnot [System.Collections.IList]) {
Fail-Json $result "groups must be a string or array"
}
$groups = $groups | ForEach-Object { ([string]$_).Trim() } | Where-Object { $_ }
If ($null -eq $groups) {
$groups = @()
}
}
$user_obj = Get-User $username
If ($state -eq 'present') {
# Add or update user
try {
If (-not $user_obj) {
$user_obj = $adsi.Create("User", $username)
If ($null -ne $password) {
$user_obj.SetPassword($password)
}
$user_obj.SetInfo()
$result.changed = $true
}
ElseIf (($null -ne $password) -and ($update_password -eq 'always')) {
# ValidateCredentials will fail if either of these are true- just force update...
If($user_obj.AccountDisabled -or $user_obj.PasswordExpired) {
$password_match = $false
}
Else {
try {
$password_match = Test-LocalCredential -Username $username -Password $password
} catch [System.ComponentModel.Win32Exception] {
Fail-Json -obj $result -message "Failed to validate the user's credentials: $($_.Exception.Message)"
}
}
If (-not $password_match) {
$user_obj.SetPassword($password)
$result.changed = $true
}
}
If (($null -ne $fullname) -and ($fullname -ne $user_obj.FullName[0])) {
$user_obj.FullName = $fullname
$result.changed = $true
}
If (($null -ne $description) -and ($description -ne $user_obj.Description[0])) {
$user_obj.Description = $description
$result.changed = $true
}
If (($null -ne $password_expired) -and ($password_expired -ne ($user_obj.PasswordExpired | ConvertTo-Bool))) {
$user_obj.PasswordExpired = If ($password_expired) { 1 } Else { 0 }
$result.changed = $true
}
If (($null -ne $password_never_expires) -and ($password_never_expires -ne (Get-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD))) {
If ($password_never_expires) {
Set-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD
}
Else {
Clear-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD
}
$result.changed = $true
}
If (($null -ne $user_cannot_change_password) -and ($user_cannot_change_password -ne (Get-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE))) {
If ($user_cannot_change_password) {
Set-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE
}
Else {
Clear-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE
}
$result.changed = $true
}
If (($null -ne $account_disabled) -and ($account_disabled -ne $user_obj.AccountDisabled)) {
$user_obj.AccountDisabled = $account_disabled
$result.changed = $true
}
If (($null -ne $account_locked) -and ($account_locked -ne $user_obj.IsAccountLocked)) {
$user_obj.IsAccountLocked = $account_locked
$result.changed = $true
}
If ($result.changed) {
$user_obj.SetInfo()
}
If ($null -ne $groups) {
[string[]]$current_groups = $user_obj.Groups() | ForEach-Object { $_.GetType().InvokeMember("Name", "GetProperty", $null, $_, $null) }
If (($groups_action -eq "remove") -or ($groups_action -eq "replace")) {
ForEach ($grp in $current_groups) {
If ((($groups_action -eq "remove") -and ($groups -contains $grp)) -or (($groups_action -eq "replace") -and ($groups -notcontains $grp))) {
$group_obj = Get-Group $grp
If ($group_obj) {
$group_obj.Remove($user_obj.Path)
$result.changed = $true
}
Else {
Fail-Json $result "group '$grp' not found"
}
}
}
}
If (($groups_action -eq "add") -or ($groups_action -eq "replace")) {
ForEach ($grp in $groups) {
If ($current_groups -notcontains $grp) {
$group_obj = Get-Group $grp
If ($group_obj) {
$group_obj.Add($user_obj.Path)
$result.changed = $true
}
Else {
Fail-Json $result "group '$grp' not found"
}
}
}
}
}
}
catch {
Fail-Json $result $_.Exception.Message
}
}
ElseIf ($state -eq 'absent') {
# Remove user
try {
If ($user_obj) {
$username = $user_obj.Name.Value
$adsi.delete("User", $user_obj.Name.Value)
$result.changed = $true
$result.msg = "User '$username' deleted successfully"
$user_obj = $null
} else {
$result.msg = "User '$username' was not found"
}
}
catch {
Fail-Json $result $_.Exception.Message
}
}
try {
If ($user_obj -and $user_obj -is [System.DirectoryServices.DirectoryEntry]) {
$user_obj.RefreshCache()
$result.name = $user_obj.Name[0]
$result.fullname = $user_obj.FullName[0]
$result.path = $user_obj.Path
$result.description = $user_obj.Description[0]
$result.password_expired = ($user_obj.PasswordExpired | ConvertTo-Bool)
$result.password_never_expires = (Get-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD)
$result.user_cannot_change_password = (Get-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE)
$result.account_disabled = $user_obj.AccountDisabled
$result.account_locked = $user_obj.IsAccountLocked
$result.sid = (New-Object System.Security.Principal.SecurityIdentifier($user_obj.ObjectSid.Value, 0)).Value
$user_groups = @()
ForEach ($grp in $user_obj.Groups()) {
$group_result = @{
name = $grp.GetType().InvokeMember("Name", "GetProperty", $null, $grp, $null)
path = $grp.GetType().InvokeMember("ADsPath", "GetProperty", $null, $grp, $null)
}
$user_groups += $group_result;
}
$result.groups = $user_groups
$result.state = "present"
}
Else {
$result.name = $username
if ($state -eq 'query') {
$result.msg = "User '$username' was not found"
}
$result.state = "absent"
}
}
catch {
Fail-Json $result $_.Exception.Message
}
Exit-Json $result

@ -1,194 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2014, Matt Martz <matt@sivel.net>, and others
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: win_user
version_added: "1.7"
short_description: Manages local Windows user accounts
description:
- Manages local Windows user accounts.
- For non-Windows targets, use the M(user) module instead.
options:
name:
description:
- Name of the user to create, remove or modify.
type: str
required: yes
fullname:
description:
- Full name of the user.
type: str
version_added: "1.9"
description:
description:
- Description of the user.
type: str
version_added: "1.9"
password:
description:
- Optionally set the user's password to this (plain text) value.
type: str
update_password:
description:
- C(always) will update passwords if they differ. C(on_create) will
only set the password for newly created users.
type: str
choices: [ always, on_create ]
default: always
version_added: "1.9"
password_expired:
description:
- C(yes) will require the user to change their password at next login.
- C(no) will clear the expired password flag.
type: bool
version_added: "1.9"
password_never_expires:
description:
- C(yes) will set the password to never expire.
- C(no) will allow the password to expire.
type: bool
version_added: "1.9"
user_cannot_change_password:
description:
- C(yes) will prevent the user from changing their password.
- C(no) will allow the user to change their password.
type: bool
version_added: "1.9"
account_disabled:
description:
- C(yes) will disable the user account.
- C(no) will clear the disabled flag.
type: bool
version_added: "1.9"
account_locked:
description:
- C(no) will unlock the user account if locked.
choices: [ 'no' ]
version_added: "1.9"
groups:
description:
- Adds or removes the user from this comma-separated list of groups,
depending on the value of I(groups_action).
- When I(groups_action) is C(replace) and I(groups) is set to the empty
string ('groups='), the user is removed from all groups.
version_added: "1.9"
groups_action:
description:
- If C(add), the user is added to each group in I(groups) where not
already a member.
- If C(replace), the user is added as a member of each group in
I(groups) and removed from any other groups.
- If C(remove), the user is removed from each group in I(groups).
type: str
choices: [ add, replace, remove ]
default: replace
version_added: "1.9"
state:
description:
- When C(absent), removes the user account if it exists.
- When C(present), creates or updates the user account.
- When C(query) (new in 1.9), retrieves the user account details
without making any changes.
type: str
choices: [ absent, present, query ]
default: present
seealso:
- module: user
- module: win_domain_membership
- module: win_domain_user
- module: win_group
- module: win_group_membership
- module: win_user_profile
author:
- Paul Durivage (@angstwad)
- Chris Church (@cchurch)
'''
EXAMPLES = r'''
- name: Ensure user bob is present
win_user:
name: bob
password: B0bP4ssw0rd
state: present
groups:
- Users
- name: Ensure user bob is absent
win_user:
name: bob
state: absent
'''
RETURN = r'''
account_disabled:
description: Whether the user is disabled.
returned: user exists
type: bool
sample: false
account_locked:
description: Whether the user is locked.
returned: user exists
type: bool
sample: false
description:
description: The description set for the user.
returned: user exists
type: str
sample: Username for test
fullname:
description: The full name set for the user.
returned: user exists
type: str
sample: Test Username
groups:
description: A list of groups and their ADSI path the user is a member of.
returned: user exists
type: list
sample: [
{
"name": "Administrators",
"path": "WinNT://WORKGROUP/USER-PC/Administrators"
}
]
name:
description: The name of the user
returned: always
type: str
sample: username
password_expired:
description: Whether the password is expired.
returned: user exists
type: bool
sample: false
password_never_expires:
description: Whether the password is set to never expire.
returned: user exists
type: bool
sample: true
path:
description: The ADSI path for the user.
returned: user exists
type: str
sample: "WinNT://WORKGROUP/USER-PC/username"
sid:
description: The SID for the user.
returned: user exists
type: str
sample: S-1-5-21-3322259488-2828151810-3939402796-1001
user_cannot_change_password:
description: Whether the user can change their own password.
returned: user exists
type: bool
sample: false
'''

@ -1,349 +0,0 @@
#!powershell
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.SID
$ErrorActionPreference = 'Stop'
$params = Parse-Args $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
$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$users = Get-AnsibleParam -obj $params -name "users" -type "list" -failifempty $true
$action = Get-AnsibleParam -obj $params -name "action" -type "str" -default "set" -validateset "add","remove","set"
$result = @{
changed = $false
added = @()
removed = @()
}
if ($diff_mode) {
$result.diff = @{}
}
$sec_helper_util = @"
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace Ansible
{
public class LsaRightHelper : IDisposable
{
// Code modified from https://gallery.technet.microsoft.com/scriptcenter/Grant-Revoke-Query-user-26e259b0
enum Access : int
{
POLICY_READ = 0x20006,
POLICY_ALL_ACCESS = 0x00F0FFF,
POLICY_EXECUTE = 0X20801,
POLICY_WRITE = 0X207F8
}
IntPtr lsaHandle;
const string LSA_DLL = "advapi32.dll";
const CharSet DEFAULT_CHAR_SET = CharSet.Unicode;
const uint STATUS_NO_MORE_ENTRIES = 0x8000001a;
const uint STATUS_NO_SUCH_PRIVILEGE = 0xc0000060;
internal sealed class Sid : IDisposable
{
public IntPtr pSid = IntPtr.Zero;
public SecurityIdentifier sid = null;
public Sid(string sidString)
{
try
{
sid = new SecurityIdentifier(sidString);
} catch
{
throw new ArgumentException(String.Format("SID string {0} could not be converted to SecurityIdentifier", sidString));
}
Byte[] buffer = new Byte[sid.BinaryLength];
sid.GetBinaryForm(buffer, 0);
pSid = Marshal.AllocHGlobal(sid.BinaryLength);
Marshal.Copy(buffer, 0, pSid, sid.BinaryLength);
}
public void Dispose()
{
if (pSid != IntPtr.Zero)
{
Marshal.FreeHGlobal(pSid);
pSid = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
~Sid() { Dispose(); }
}
[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public IntPtr ObjectName;
public int Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
[StructLayout(LayoutKind.Sequential, CharSet = DEFAULT_CHAR_SET)]
private struct LSA_UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string Buffer;
}
[StructLayout(LayoutKind.Sequential)]
private struct LSA_ENUMERATION_INFORMATION
{
public IntPtr Sid;
}
[DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)]
private static extern uint LsaOpenPolicy(
LSA_UNICODE_STRING[] SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
int AccessMask,
out IntPtr PolicyHandle
);
[DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)]
private static extern uint LsaAddAccountRights(
IntPtr PolicyHandle,
IntPtr pSID,
LSA_UNICODE_STRING[] UserRights,
int CountOfRights
);
[DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)]
private static extern uint LsaRemoveAccountRights(
IntPtr PolicyHandle,
IntPtr pSID,
bool AllRights,
LSA_UNICODE_STRING[] UserRights,
int CountOfRights
);
[DllImport(LSA_DLL, CharSet = DEFAULT_CHAR_SET, SetLastError = true)]
private static extern uint LsaEnumerateAccountsWithUserRight(
IntPtr PolicyHandle,
LSA_UNICODE_STRING[] UserRights,
out IntPtr EnumerationBuffer,
out ulong CountReturned
);
[DllImport(LSA_DLL)]
private static extern int LsaNtStatusToWinError(int NTSTATUS);
[DllImport(LSA_DLL)]
private static extern int LsaClose(IntPtr PolicyHandle);
[DllImport(LSA_DLL)]
private static extern int LsaFreeMemory(IntPtr Buffer);
public LsaRightHelper()
{
LSA_OBJECT_ATTRIBUTES lsaAttr;
lsaAttr.RootDirectory = IntPtr.Zero;
lsaAttr.ObjectName = IntPtr.Zero;
lsaAttr.Attributes = 0;
lsaAttr.SecurityDescriptor = IntPtr.Zero;
lsaAttr.SecurityQualityOfService = IntPtr.Zero;
lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES));
lsaHandle = IntPtr.Zero;
LSA_UNICODE_STRING[] system = new LSA_UNICODE_STRING[1];
system[0] = InitLsaString("");
uint ret = LsaOpenPolicy(system, ref lsaAttr, (int)Access.POLICY_ALL_ACCESS, out lsaHandle);
if (ret != 0)
throw new Win32Exception(LsaNtStatusToWinError((int)ret));
}
public void AddPrivilege(string sidString, string privilege)
{
uint ret = 0;
using (Sid sid = new Sid(sidString))
{
LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1];
privileges[0] = InitLsaString(privilege);
ret = LsaAddAccountRights(lsaHandle, sid.pSid, privileges, 1);
}
if (ret != 0)
throw new Win32Exception(LsaNtStatusToWinError((int)ret));
}
public void RemovePrivilege(string sidString, string privilege)
{
uint ret = 0;
using (Sid sid = new Sid(sidString))
{
LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1];
privileges[0] = InitLsaString(privilege);
ret = LsaRemoveAccountRights(lsaHandle, sid.pSid, false, privileges, 1);
}
if (ret != 0)
throw new Win32Exception(LsaNtStatusToWinError((int)ret));
}
public string[] EnumerateAccountsWithUserRight(string privilege)
{
uint ret = 0;
ulong count = 0;
LSA_UNICODE_STRING[] rights = new LSA_UNICODE_STRING[1];
rights[0] = InitLsaString(privilege);
IntPtr buffer = IntPtr.Zero;
ret = LsaEnumerateAccountsWithUserRight(lsaHandle, rights, out buffer, out count);
switch (ret)
{
case 0:
string[] accounts = new string[count];
for (int i = 0; i < (int)count; i++)
{
LSA_ENUMERATION_INFORMATION LsaInfo = (LSA_ENUMERATION_INFORMATION)Marshal.PtrToStructure(
IntPtr.Add(buffer, i * Marshal.SizeOf(typeof(LSA_ENUMERATION_INFORMATION))),
typeof(LSA_ENUMERATION_INFORMATION));
accounts[i] = new SecurityIdentifier(LsaInfo.Sid).ToString();
}
LsaFreeMemory(buffer);
return accounts;
case STATUS_NO_MORE_ENTRIES:
return new string[0];
case STATUS_NO_SUCH_PRIVILEGE:
throw new ArgumentException(String.Format("Invalid privilege {0} not found in LSA database", privilege));
default:
throw new Win32Exception(LsaNtStatusToWinError((int)ret));
}
}
static LSA_UNICODE_STRING InitLsaString(string s)
{
// Unicode strings max. 32KB
if (s.Length > 0x7ffe)
throw new ArgumentException("String too long");
LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING();
lus.Buffer = s;
lus.Length = (ushort)(s.Length * sizeof(char));
lus.MaximumLength = (ushort)(lus.Length + sizeof(char));
return lus;
}
public void Dispose()
{
if (lsaHandle != IntPtr.Zero)
{
LsaClose(lsaHandle);
lsaHandle = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
~LsaRightHelper() { Dispose(); }
}
}
"@
$original_tmp = $env:TMP
$env:TMP = $_remote_tmp
Add-Type -TypeDefinition $sec_helper_util
$env:TMP = $original_tmp
Function Compare-UserList($existing_users, $new_users) {
$added_users = [String[]]@()
$removed_users = [String[]]@()
if ($action -eq "add") {
$added_users = [Linq.Enumerable]::Except($new_users, $existing_users)
} elseif ($action -eq "remove") {
$removed_users = [Linq.Enumerable]::Intersect($new_users, $existing_users)
} else {
$added_users = [Linq.Enumerable]::Except($new_users, $existing_users)
$removed_users = [Linq.Enumerable]::Except($existing_users, $new_users)
}
$change_result = @{
added = $added_users
removed = $removed_users
}
return $change_result
}
# C# class we can use to enumerate/add/remove rights
$lsa_helper = New-Object -TypeName Ansible.LsaRightHelper
$new_users = [System.Collections.ArrayList]@()
foreach ($user in $users) {
$user_sid = Convert-ToSID -account_name $user
$new_users.Add($user_sid) > $null
}
$new_users = [String[]]$new_users.ToArray()
try {
$existing_users = $lsa_helper.EnumerateAccountsWithUserRight($name)
} catch [ArgumentException] {
Fail-Json -obj $result -message "the specified right $name is not a valid right"
} catch {
Fail-Json -obj $result -message "failed to enumerate existing accounts with right: $($_.Exception.Message)"
}
$change_result = Compare-UserList -existing_users $existing_users -new_user $new_users
if (($change_result.added.Length -gt 0) -or ($change_result.removed.Length -gt 0)) {
$result.changed = $true
$diff_text = "[$name]`n"
# used in diff mode calculation
$new_user_list = [System.Collections.ArrayList]$existing_users
foreach ($user in $change_result.removed) {
if (-not $check_mode) {
$lsa_helper.RemovePrivilege($user, $name)
}
$user_name = Convert-FromSID -sid $user
$result.removed += $user_name
$diff_text += "-$user_name`n"
$new_user_list.Remove($user) > $null
}
foreach ($user in $change_result.added) {
if (-not $check_mode) {
$lsa_helper.AddPrivilege($user, $name)
}
$user_name = Convert-FromSID -sid $user
$result.added += $user_name
$diff_text += "+$user_name`n"
$new_user_list.Add($user) > $null
}
if ($diff_mode) {
if ($new_user_list.Count -eq 0) {
$diff_text = "-$diff_text"
} else {
if ($existing_users.Count -eq 0) {
$diff_text = "+$diff_text"
}
}
$result.diff.prepared = $diff_text
}
}
Exit-Json $result

@ -1,108 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_user_right
version_added: '2.4'
short_description: Manage Windows User Rights
description:
- Add, remove or set User Rights for a group or users or groups.
- You can set user rights for both local and domain accounts.
options:
name:
description:
- The name of the User Right as shown by the C(Constant Name) value from
U(https://technet.microsoft.com/en-us/library/dd349804.aspx).
- The module will return an error if the right is invalid.
type: str
required: yes
users:
description:
- A list of users or groups to add/remove on the User Right.
- These can be in the form DOMAIN\user-group, user-group@DOMAIN.COM for
domain users/groups.
- For local users/groups it can be in the form user-group, .\user-group,
SERVERNAME\user-group where SERVERNAME is the name of the remote server.
- You can also add special local accounts like SYSTEM and others.
- Can be set to an empty list with I(action=set) to remove all accounts
from the right.
type: list
required: yes
action:
description:
- C(add) will add the users/groups to the existing right.
- C(remove) will remove the users/groups from the existing right.
- C(set) will replace the users/groups of the existing right.
type: str
default: set
choices: [ add, remove, set ]
notes:
- If the server is domain joined this module can change a right but if a GPO
governs this right then the changes won't last.
seealso:
- module: win_group
- module: win_group_membership
- module: win_user
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
---
- name: Replace the entries of Deny log on locally
win_user_right:
name: SeDenyInteractiveLogonRight
users:
- Guest
- Users
action: set
- name: Add account to Log on as a service
win_user_right:
name: SeServiceLogonRight
users:
- .\Administrator
- '{{ansible_hostname}}\local-user'
action: add
- name: Remove accounts who can create Symbolic links
win_user_right:
name: SeCreateSymbolicLinkPrivilege
users:
- SYSTEM
- Administrators
- DOMAIN\User
- group@DOMAIN.COM
action: remove
- name: Remove all accounts who cannot log on remote interactively
win_user_right:
name: SeDenyRemoteInteractiveLogonRight
users: []
'''
RETURN = r'''
added:
description: A list of accounts that were added to the right, this is empty
if no accounts were added.
returned: success
type: list
sample: ["NT AUTHORITY\\SYSTEM", "DOMAIN\\User"]
removed:
description: A list of accounts that were removed from the right, this is
empty if no accounts were removed.
returned: success
type: list
sample: ["SERVERNAME\\Administrator", "BUILTIN\\Administrators"]
'''

@ -1,259 +0,0 @@
#!powershell
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.FileUtil
$ErrorActionPreference = "Stop"
$params = Parse-Args -arguments $args -supports_check_mode $true
$connect_timeout = Get-AnsibleParam -obj $params -name "connect_timeout" -type "int" -default 5
$delay = Get-AnsibleParam -obj $params -name "delay" -type "int"
$exclude_hosts = Get-AnsibleParam -obj $params -name "exclude_hosts" -type "list"
$hostname = Get-AnsibleParam -obj $params -name "host" -type "str" -default "127.0.0.1"
$path = Get-AnsibleParam -obj $params -name "path" -type "path"
$port = Get-AnsibleParam -obj $params -name "port" -type "int"
$regex = Get-AnsibleParam -obj $params -name "regex" -type "str" -aliases "search_regex","regexp"
$sleep = Get-AnsibleParam -obj $params -name "sleep" -type "int" -default 1
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "started" -validateset "present","started","stopped","absent","drained"
$timeout = Get-AnsibleParam -obj $params -name "timeout" -type "int" -default 300
$result = @{
changed = $false
elapsed = 0
}
# validate the input with the various options
if ($null -ne $port -and $null -ne $path) {
Fail-Json $result "port and path parameter can not both be passed to win_wait_for"
}
if ($null -ne $exclude_hosts -and $state -ne "drained") {
Fail-Json $result "exclude_hosts should only be with state=drained"
}
if ($null -ne $path) {
if ($state -in @("stopped","drained")) {
Fail-Json $result "state=$state should only be used for checking a port in the win_wait_for module"
}
if ($null -ne $exclude_hosts) {
Fail-Json $result "exclude_hosts should only be used when checking a port and state=drained in the win_wait_for module"
}
}
if ($null -ne $port) {
if ($null -ne $regex) {
Fail-Json $result "regex should by used when checking a string in a file in the win_wait_for module"
}
if ($null -ne $exclude_hosts -and $state -ne "drained") {
Fail-Json $result "exclude_hosts should be used when state=drained in the win_wait_for module"
}
}
Function Test-Port($hostname, $port) {
$timeout = $connect_timeout * 1000
$socket = New-Object -TypeName System.Net.Sockets.TcpClient
$connect = $socket.BeginConnect($hostname, $port, $null, $null)
$wait = $connect.AsyncWaitHandle.WaitOne($timeout, $false)
if ($wait) {
try {
$socket.EndConnect($connect) | Out-Null
$valid = $true
} catch {
$valid = $false
}
} else {
$valid = $false
}
$socket.Close()
$socket.Dispose()
$valid
}
Function Get-PortConnections($hostname, $port) {
$connections = @()
$conn_info = [Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
if ($hostname -eq "0.0.0.0") {
$active_connections = $conn_info.GetActiveTcpConnections() | Where-Object { $_.LocalEndPoint.Port -eq $port }
} else {
$active_connections = $conn_info.GetActiveTcpConnections() | Where-Object { $_.LocalEndPoint.Address -eq $hostname -and $_.LocalEndPoint.Port -eq $port }
}
if ($null -ne $active_connections) {
foreach ($active_connection in $active_connections) {
$connections += $active_connection.RemoteEndPoint.Address
}
}
$connections
}
$module_start = Get-Date
if ($null -ne $delay) {
Start-Sleep -Seconds $delay
}
$attempts = 0
if ($null -eq $path -and $null -eq $port -and $state -ne "drained") {
Start-Sleep -Seconds $timeout
} elseif ($null -ne $path) {
if ($state -in @("present", "started")) {
# check if the file exists or string exists in file
$start_time = Get-Date
$complete = $false
while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
$attempts += 1
if (Test-AnsiblePath -Path $path) {
if ($null -eq $regex) {
$complete = $true
break
} else {
$file_contents = Get-Content -Path $path -Raw
if ($file_contents -match $regex) {
$complete = $true
break
}
}
}
Start-Sleep -Seconds $sleep
}
if ($complete -eq $false) {
$result.elapsed = ((Get-Date) - $module_start).TotalSeconds
$result.wait_attempts = $attempts
if ($null -eq $regex) {
Fail-Json $result "timeout while waiting for file $path to be present"
} else {
Fail-Json $result "timeout while waiting for string regex $regex in file $path to match"
}
}
} elseif ($state -in @("absent")) {
# check if the file is deleted or string doesn't exist in file
$start_time = Get-Date
$complete = $false
while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
$attempts += 1
if (Test-AnsiblePath -Path $path) {
if ($null -ne $regex) {
$file_contents = Get-Content -Path $path -Raw
if ($file_contents -notmatch $regex) {
$complete = $true
break
}
}
} else {
$complete = $true
break
}
Start-Sleep -Seconds $sleep
}
if ($complete -eq $false) {
$result.elapsed = ((Get-Date) - $module_start).TotalSeconds
$result.wait_attempts = $attempts
if ($null -eq $regex) {
Fail-Json $result "timeout while waiting for file $path to be absent"
} else {
Fail-Json $result "timeout while waiting for string regex $regex in file $path to not match"
}
}
}
} elseif ($null -ne $port) {
if ($state -in @("started","present")) {
# check that the port is online and is listening
$start_time = Get-Date
$complete = $false
while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
$attempts += 1
$port_result = Test-Port -hostname $hostname -port $port
if ($port_result -eq $true) {
$complete = $true
break
}
Start-Sleep -Seconds $sleep
}
if ($complete -eq $false) {
$result.elapsed = ((Get-Date) - $module_start).TotalSeconds
$result.wait_attempts = $attempts
Fail-Json $result "timeout while waiting for $($hostname):$port to start listening"
}
} elseif ($state -in @("stopped","absent")) {
# check that the port is offline and is not listening
$start_time = Get-Date
$complete = $false
while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
$attempts += 1
$port_result = Test-Port -hostname $hostname -port $port
if ($port_result -eq $false) {
$complete = $true
break
}
Start-Sleep -Seconds $sleep
}
if ($complete -eq $false) {
$result.elapsed = ((Get-Date) - $module_start).TotalSeconds
$result.wait_attempts = $attempts
Fail-Json $result "timeout while waiting for $($hostname):$port to stop listening"
}
} elseif ($state -eq "drained") {
# check that the local port is online but has no active connections
$start_time = Get-Date
$complete = $false
while (((Get-Date) - $start_time).TotalSeconds -lt $timeout) {
$attempts += 1
$active_connections = Get-PortConnections -hostname $hostname -port $port
if ($null -eq $active_connections) {
$complete = $true
break
} elseif ($active_connections.Count -eq 0) {
# no connections on port
$complete = $true
break
} else {
# there are listeners, check if we should ignore any hosts
if ($null -ne $exclude_hosts) {
$connection_info = $active_connections
foreach ($exclude_host in $exclude_hosts) {
try {
$exclude_ips = [System.Net.Dns]::GetHostAddresses($exclude_host) | ForEach-Object { Write-Output $_.IPAddressToString }
$connection_info = $connection_info | Where-Object { $_ -notin $exclude_ips }
} catch { # ignore invalid hostnames
Add-Warning -obj $result -message "Invalid hostname specified $exclude_host"
}
}
if ($connection_info.Count -eq 0) {
$complete = $true
break
}
}
}
Start-Sleep -Seconds $sleep
}
if ($complete -eq $false) {
$result.elapsed = ((Get-Date) - $module_start).TotalSeconds
$result.wait_attempts = $attempts
Fail-Json $result "timeout while waiting for $($hostname):$port to drain"
}
}
}
$result.elapsed = ((Get-Date) - $module_start).TotalSeconds
$result.wait_attempts = $attempts
Exit-Json $result

@ -1,155 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub, actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_wait_for
version_added: '2.4'
short_description: Waits for a condition before continuing
description:
- You can wait for a set amount of time C(timeout), this is the default if
nothing is specified.
- Waiting for a port to become available is useful for when services are not
immediately available after their init scripts return which is true of
certain Java application servers.
- You can wait for a file to exist or not exist on the filesystem.
- This module can also be used to wait for a regex match string to be present
in a file.
- You can wait for active connections to be closed before continuing on a
local port.
options:
connect_timeout:
description:
- The maximum number of seconds to wait for a connection to happen before
closing and retrying.
type: int
default: 5
delay:
description:
- The number of seconds to wait before starting to poll.
type: int
exclude_hosts:
description:
- The list of hosts or IPs to ignore when looking for active TCP
connections when C(state=drained).
type: list
host:
description:
- A resolvable hostname or IP address to wait for.
- If C(state=drained) then it will only check for connections on the IP
specified, you can use '0.0.0.0' to use all host IPs.
type: str
default: '127.0.0.1'
path:
description:
- The path to a file on the filesystem to check.
- If C(state) is present or started then it will wait until the file
exists.
- If C(state) is absent then it will wait until the file does not exist.
type: path
port:
description:
- The port number to poll on C(host).
type: int
regex:
description:
- Can be used to match a string in a file.
- If C(state) is present or started then it will wait until the regex
matches.
- If C(state) is absent then it will wait until the regex does not match.
- Defaults to a multiline regex.
type: str
aliases: [ "search_regex", "regexp" ]
sleep:
description:
- Number of seconds to sleep between checks.
type: int
default: 1
state:
description:
- When checking a port, C(started) will ensure the port is open, C(stopped)
will check that is it closed and C(drained) will check for active
connections.
- When checking for a file or a search string C(present) or C(started) will
ensure that the file or string is present, C(absent) will check that the
file or search string is absent or removed.
type: str
choices: [ absent, drained, present, started, stopped ]
default: started
timeout:
description:
- The maximum number of seconds to wait for.
type: int
default: 300
seealso:
- module: wait_for
- module: win_wait_for_process
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Wait 300 seconds for port 8000 to become open on the host, don't start checking for 10 seconds
win_wait_for:
port: 8000
delay: 10
- name: Wait 150 seconds for port 8000 of any IP to close active connections
win_wait_for:
host: 0.0.0.0
port: 8000
state: drained
timeout: 150
- name: Wait for port 8000 of any IP to close active connection, ignoring certain hosts
win_wait_for:
host: 0.0.0.0
port: 8000
state: drained
exclude_hosts: ['10.2.1.2', '10.2.1.3']
- name: Wait for file C:\temp\log.txt to exist before continuing
win_wait_for:
path: C:\temp\log.txt
- name: Wait until process complete is in the file before continuing
win_wait_for:
path: C:\temp\log.txt
regex: process complete
- name: Wait until file is removed
win_wait_for:
path: C:\temp\log.txt
state: absent
- name: Wait until port 1234 is offline but try every 10 seconds
win_wait_for:
port: 1234
state: absent
sleep: 10
'''
RETURN = r'''
wait_attempts:
description: The number of attempts to poll the file or port before module
finishes.
returned: always
type: int
sample: 1
elapsed:
description: The elapsed seconds between the start of poll and the end of the
module. This includes the delay if the option is set.
returned: always
type: float
sample: 2.1406487
'''

@ -1,837 +0,0 @@
#!powershell
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.CamelConversion
$ErrorActionPreference = "Stop"
$params = Parse-Args $args -supports_check_mode $true
$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP
$session_util = @'
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
namespace Ansible
{
public class SessionInfo
{
// SECURITY_LOGON_SESSION_DATA
public UInt64 LogonId { get; internal set; }
public Sid Account { get; internal set; }
public string LoginDomain { get; internal set; }
public string AuthenticationPackage { get; internal set; }
public SECURITY_LOGON_TYPE LogonType { get; internal set; }
public string LoginTime { get; internal set; }
public string LogonServer { get; internal set; }
public string DnsDomainName { get; internal set; }
public string Upn { get; internal set; }
public ArrayList UserFlags { get; internal set; }
// TOKEN_STATISTICS
public SECURITY_IMPERSONATION_LEVEL ImpersonationLevel { get; internal set; }
public TOKEN_TYPE TokenType { get; internal set; }
// TOKEN_GROUPS
public ArrayList Groups { get; internal set; }
public ArrayList Rights { get; internal set; }
// TOKEN_MANDATORY_LABEL
public Sid Label { get; internal set; }
// TOKEN_PRIVILEGES
public Hashtable Privileges { get; internal set; }
}
public class Win32Exception : System.ComponentModel.Win32Exception
{
private string _msg;
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
public Win32Exception(int errorCode, string message) : base(errorCode)
{
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
}
public override string Message { get { return _msg; } }
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr buffer;
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart;
public Int32 HighPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_LOGON_SESSION_DATA
{
public UInt32 Size;
public LUID LogonId;
public LSA_UNICODE_STRING Username;
public LSA_UNICODE_STRING LoginDomain;
public LSA_UNICODE_STRING AuthenticationPackage;
public SECURITY_LOGON_TYPE LogonType;
public UInt32 Session;
public IntPtr Sid;
public UInt64 LoginTime;
public LSA_UNICODE_STRING LogonServer;
public LSA_UNICODE_STRING DnsDomainName;
public LSA_UNICODE_STRING Upn;
public UInt32 UserFlags;
public LSA_LAST_INTER_LOGON_INFO LastLogonInfo;
public LSA_UNICODE_STRING LogonScript;
public LSA_UNICODE_STRING ProfilePath;
public LSA_UNICODE_STRING HomeDirectory;
public LSA_UNICODE_STRING HomeDirectoryDrive;
public UInt64 LogoffTime;
public UInt64 KickOffTime;
public UInt64 PasswordLastSet;
public UInt64 PasswordCanChange;
public UInt64 PasswordMustChange;
}
[StructLayout(LayoutKind.Sequential)]
public struct LSA_LAST_INTER_LOGON_INFO
{
public UInt64 LastSuccessfulLogon;
public UInt64 LastFailedLogon;
public UInt32 FailedAttemptCountSinceLastSuccessfulLogon;
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
public enum SECURITY_LOGON_TYPE
{
System = 0, // Used only by the Sytem account
Interactive = 2,
Network,
Batch,
Service,
Proxy,
Unlock,
NetworkCleartext,
NewCredentials,
RemoteInteractive,
CachedInteractive,
CachedRemoteInteractive,
CachedUnlock
}
[Flags]
public enum TokenGroupAttributes : uint
{
SE_GROUP_ENABLED = 0x00000004,
SE_GROUP_ENABLED_BY_DEFAULT = 0x00000002,
SE_GROUP_INTEGRITY = 0x00000020,
SE_GROUP_INTEGRITY_ENABLED = 0x00000040,
SE_GROUP_LOGON_ID = 0xC0000000,
SE_GROUP_MANDATORY = 0x00000001,
SE_GROUP_OWNER = 0x00000008,
SE_GROUP_RESOURCE = 0x20000000,
SE_GROUP_USE_FOR_DENY_ONLY = 0x00000010,
}
[Flags]
public enum UserFlags : uint
{
LOGON_OPTIMIZED = 0x4000,
LOGON_WINLOGON = 0x8000,
LOGON_PKINIT = 0x10000,
LOGON_NOT_OPTMIZED = 0x20000,
}
[StructLayout(LayoutKind.Sequential)]
public struct SID_AND_ATTRIBUTES
{
public IntPtr Sid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_GROUPS
{
public UInt32 GroupCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public SID_AND_ATTRIBUTES[] Groups;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_MANDATORY_LABEL
{
public SID_AND_ATTRIBUTES Label;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_STATISTICS
{
public LUID TokenId;
public LUID AuthenticationId;
public UInt64 ExpirationTime;
public TOKEN_TYPE TokenType;
public SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
public UInt32 DynamicCharged;
public UInt32 DynamicAvailable;
public UInt32 GroupCount;
public UInt32 PrivilegeCount;
public LUID ModifiedId;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_PRIVILEGES
{
public UInt32 PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
}
public class AccessToken : IDisposable
{
public enum TOKEN_INFORMATION_CLASS
{
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin,
TokenElevationType,
TokenLinkedToken,
TokenElevation,
TokenHasRestrictions,
TokenAccessInformation,
TokenVirtualizationAllowed,
TokenVirtualizationEnabled,
TokenIntegrityLevel,
TokenUIAccess,
TokenMandatoryPolicy,
TokenLogonSid,
TokenIsAppContainer,
TokenCapabilities,
TokenAppContainerSid,
TokenAppContainerNumber,
TokenUserClaimAttributes,
TokenDeviceClaimAttributes,
TokenRestrictedUserClaimAttributes,
TokenRestrictedDeviceClaimAttributes,
TokenDeviceGroups,
TokenRestrictedDeviceGroups,
TokenSecurityAttributes,
TokenIsRestricted,
MaxTokenInfoClass
}
public IntPtr hToken = IntPtr.Zero;
[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
TokenAccessLevels DesiredAccess,
out IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool GetTokenInformation(
IntPtr TokenHandle,
TOKEN_INFORMATION_CLASS TokenInformationClass,
IntPtr TokenInformation,
UInt32 TokenInformationLength,
out UInt32 ReturnLength);
public AccessToken(TokenAccessLevels tokenAccessLevels)
{
IntPtr currentProcess = GetCurrentProcess();
if (!OpenProcessToken(currentProcess, tokenAccessLevels, out hToken))
throw new Win32Exception("OpenProcessToken() for current process failed");
}
public IntPtr GetTokenInformation<T>(out T tokenInformation, TOKEN_INFORMATION_CLASS tokenClass)
{
UInt32 tokenLength = 0;
GetTokenInformation(hToken, tokenClass, IntPtr.Zero, 0, out tokenLength);
IntPtr infoPtr = Marshal.AllocHGlobal((int)tokenLength);
if (!GetTokenInformation(hToken, tokenClass, infoPtr, tokenLength, out tokenLength))
throw new Win32Exception(String.Format("GetTokenInformation() data for {0} failed", tokenClass.ToString()));
tokenInformation = (T)Marshal.PtrToStructure(infoPtr, typeof(T));
return infoPtr;
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
~AccessToken() { Dispose(); }
}
public class LsaHandle : IDisposable
{
[Flags]
public enum DesiredAccess : uint
{
POLICY_VIEW_LOCAL_INFORMATION = 0x00000001,
POLICY_VIEW_AUDIT_INFORMATION = 0x00000002,
POLICY_GET_PRIVATE_INFORMATION = 0x00000004,
POLICY_TRUST_ADMIN = 0x00000008,
POLICY_CREATE_ACCOUNT = 0x00000010,
POLICY_CREATE_SECRET = 0x00000020,
POLICY_CREATE_PRIVILEGE = 0x00000040,
POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080,
POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100,
POLICY_AUDIT_LOG_ADMIN = 0x00000200,
POLICY_SERVER_ADMIN = 0x00000400,
POLICY_LOOKUP_NAMES = 0x00000800,
POLICY_NOTIFICATION = 0x00001000
}
public IntPtr handle = IntPtr.Zero;
[DllImport("advapi32.dll", SetLastError = true)]
private static extern uint LsaOpenPolicy(
LSA_UNICODE_STRING[] SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
DesiredAccess AccessMask,
out IntPtr PolicyHandle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern uint LsaClose(
IntPtr ObjectHandle);
[DllImport("advapi32.dll", SetLastError = false)]
private static extern int LsaNtStatusToWinError(
uint Status);
[StructLayout(LayoutKind.Sequential)]
public struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public IntPtr ObjectName;
public int Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
public LsaHandle(DesiredAccess desiredAccess)
{
LSA_OBJECT_ATTRIBUTES lsaAttr;
lsaAttr.RootDirectory = IntPtr.Zero;
lsaAttr.ObjectName = IntPtr.Zero;
lsaAttr.Attributes = 0;
lsaAttr.SecurityDescriptor = IntPtr.Zero;
lsaAttr.SecurityQualityOfService = IntPtr.Zero;
lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES));
LSA_UNICODE_STRING[] system = new LSA_UNICODE_STRING[1];
system[0].buffer = IntPtr.Zero;
uint res = LsaOpenPolicy(system, ref lsaAttr, desiredAccess, out handle);
if (res != 0)
throw new Win32Exception(LsaNtStatusToWinError(res), "LsaOpenPolicy() failed");
}
public void Dispose()
{
if (handle != IntPtr.Zero)
{
LsaClose(handle);
handle = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
~LsaHandle() { Dispose(); }
}
public class Sid
{
public string SidString { get; internal set; }
public string DomainName { get; internal set; }
public string AccountName { get; internal set; }
public SID_NAME_USE SidType { get; internal set; }
public enum SID_NAME_USE
{
SidTypeUser = 1,
SidTypeGroup,
SidTypeDomain,
SidTypeAlias,
SidTypeWellKnownGroup,
SidTypeDeletedAccount,
SidTypeInvalid,
SidTypeUnknown,
SidTypeComputer,
SidTypeLabel,
SidTypeLogon,
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LookupAccountSid(
string lpSystemName,
[MarshalAs(UnmanagedType.LPArray)]
byte[] Sid,
StringBuilder lpName,
ref UInt32 cchName,
StringBuilder ReferencedDomainName,
ref UInt32 cchReferencedDomainName,
out SID_NAME_USE peUse);
public Sid(IntPtr sidPtr)
{
SecurityIdentifier sid;
try
{
sid = new SecurityIdentifier(sidPtr);
}
catch (Exception e)
{
throw new ArgumentException(String.Format("Failed to cast IntPtr to SecurityIdentifier: {0}", e));
}
SetSidInfo(sid);
}
public Sid(SecurityIdentifier sid)
{
SetSidInfo(sid);
}
public override string ToString()
{
return SidString;
}
private void SetSidInfo(SecurityIdentifier sid)
{
byte[] sidBytes = new byte[sid.BinaryLength];
sid.GetBinaryForm(sidBytes, 0);
StringBuilder lpName = new StringBuilder();
UInt32 cchName = 0;
StringBuilder referencedDomainName = new StringBuilder();
UInt32 cchReferencedDomainName = 0;
SID_NAME_USE peUse;
LookupAccountSid(null, sidBytes, lpName, ref cchName, referencedDomainName, ref cchReferencedDomainName, out peUse);
lpName.EnsureCapacity((int)cchName);
referencedDomainName.EnsureCapacity((int)cchReferencedDomainName);
SidString = sid.ToString();
if (!LookupAccountSid(null, sidBytes, lpName, ref cchName, referencedDomainName, ref cchReferencedDomainName, out peUse))
{
int lastError = Marshal.GetLastWin32Error();
if (lastError != 1332 && lastError != 1789) // Fails to lookup Logon Sid
{
throw new Win32Exception(lastError, String.Format("LookupAccountSid() failed for SID: {0} {1}", sid.ToString(), lastError));
}
else if (SidString.StartsWith("S-1-5-5-"))
{
AccountName = String.Format("LogonSessionId_{0}", SidString.Substring(8));
DomainName = "NT AUTHORITY";
SidType = SID_NAME_USE.SidTypeLogon;
}
else
{
AccountName = null;
DomainName = null;
SidType = SID_NAME_USE.SidTypeUnknown;
}
}
else
{
AccountName = lpName.ToString();
DomainName = referencedDomainName.ToString();
SidType = peUse;
}
}
}
public class SessionUtil
{
[DllImport("secur32.dll", SetLastError = false)]
private static extern uint LsaFreeReturnBuffer(
IntPtr Buffer);
[DllImport("secur32.dll", SetLastError = false)]
private static extern uint LsaEnumerateLogonSessions(
out UInt64 LogonSessionCount,
out IntPtr LogonSessionList);
[DllImport("secur32.dll", SetLastError = false)]
private static extern uint LsaGetLogonSessionData(
IntPtr LogonId,
out IntPtr ppLogonSessionData);
[DllImport("advapi32.dll", SetLastError = false)]
private static extern int LsaNtStatusToWinError(
uint Status);
[DllImport("advapi32", SetLastError = true)]
private static extern uint LsaEnumerateAccountRights(
IntPtr PolicyHandle,
IntPtr AccountSid,
out IntPtr UserRights,
out UInt64 CountOfRights);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LookupPrivilegeName(
string lpSystemName,
ref LUID lpLuid,
StringBuilder lpName,
ref UInt32 cchName);
private const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001;
private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
private const UInt32 STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034;
private const UInt32 STATUS_ACCESS_DENIED = 0xC0000022;
public static SessionInfo GetSessionInfo()
{
AccessToken accessToken = new AccessToken(TokenAccessLevels.Query);
// Get Privileges
Hashtable privilegeInfo = new Hashtable();
TOKEN_PRIVILEGES privileges;
IntPtr privilegesPtr = accessToken.GetTokenInformation(out privileges, AccessToken.TOKEN_INFORMATION_CLASS.TokenPrivileges);
LUID_AND_ATTRIBUTES[] luidAndAttributes = new LUID_AND_ATTRIBUTES[privileges.PrivilegeCount];
try
{
PtrToStructureArray(luidAndAttributes, privilegesPtr.ToInt64() + Marshal.SizeOf(privileges.PrivilegeCount));
}
finally
{
Marshal.FreeHGlobal(privilegesPtr);
}
foreach (LUID_AND_ATTRIBUTES luidAndAttribute in luidAndAttributes)
{
LUID privLuid = luidAndAttribute.Luid;
UInt32 privNameLen = 0;
StringBuilder privName = new StringBuilder();
LookupPrivilegeName(null, ref privLuid, null, ref privNameLen);
privName.EnsureCapacity((int)(privNameLen + 1));
if (!LookupPrivilegeName(null, ref privLuid, privName, ref privNameLen))
throw new Win32Exception("LookupPrivilegeName() failed");
string state = "disabled";
if ((luidAndAttribute.Attributes & SE_PRIVILEGE_ENABLED) == SE_PRIVILEGE_ENABLED)
state = "enabled";
if ((luidAndAttribute.Attributes & SE_PRIVILEGE_ENABLED_BY_DEFAULT) == SE_PRIVILEGE_ENABLED_BY_DEFAULT)
state = "enabled-by-default";
privilegeInfo.Add(privName.ToString(), state);
}
// Get Current Process LogonSID, User Rights and Groups
ArrayList userRights = new ArrayList();
ArrayList userGroups = new ArrayList();
TOKEN_GROUPS groups;
IntPtr groupsPtr = accessToken.GetTokenInformation(out groups, AccessToken.TOKEN_INFORMATION_CLASS.TokenGroups);
SID_AND_ATTRIBUTES[] sidAndAttributes = new SID_AND_ATTRIBUTES[groups.GroupCount];
LsaHandle lsaHandle = null;
// We can only get rights if we are an admin
if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
lsaHandle = new LsaHandle(LsaHandle.DesiredAccess.POLICY_LOOKUP_NAMES);
try
{
PtrToStructureArray(sidAndAttributes, groupsPtr.ToInt64() + IntPtr.Size);
foreach (SID_AND_ATTRIBUTES sidAndAttribute in sidAndAttributes)
{
TokenGroupAttributes attributes = (TokenGroupAttributes)sidAndAttribute.Attributes;
if (attributes.HasFlag(TokenGroupAttributes.SE_GROUP_ENABLED) && lsaHandle != null)
{
ArrayList rights = GetAccountRights(lsaHandle.handle, sidAndAttribute.Sid);
foreach (string right in rights)
{
// Includes both Privileges and Account Rights, only add the ones with Logon in the name
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb545671(v=vs.85).aspx
if (!userRights.Contains(right) && right.Contains("Logon"))
userRights.Add(right);
}
}
// Do not include the Logon SID in the groups category
if (!attributes.HasFlag(TokenGroupAttributes.SE_GROUP_LOGON_ID))
{
Hashtable groupInfo = new Hashtable();
Sid group = new Sid(sidAndAttribute.Sid);
ArrayList groupAttributes = new ArrayList();
foreach (TokenGroupAttributes attribute in Enum.GetValues(typeof(TokenGroupAttributes)))
{
if (attributes.HasFlag(attribute))
{
string attributeName = attribute.ToString().Substring(9);
attributeName = attributeName.Replace('_', ' ');
attributeName = attributeName.First().ToString().ToUpper() + attributeName.Substring(1).ToLower();
groupAttributes.Add(attributeName);
}
}
// Using snake_case here as I can't generically convert all dict keys in PS (see Privileges)
groupInfo.Add("sid", group.SidString);
groupInfo.Add("domain_name", group.DomainName);
groupInfo.Add("account_name", group.AccountName);
groupInfo.Add("type", group.SidType);
groupInfo.Add("attributes", groupAttributes);
userGroups.Add(groupInfo);
}
}
}
finally
{
Marshal.FreeHGlobal(groupsPtr);
if (lsaHandle != null)
lsaHandle.Dispose();
}
// Get Integrity Level
Sid integritySid = null;
TOKEN_MANDATORY_LABEL mandatoryLabel;
IntPtr mandatoryLabelPtr = accessToken.GetTokenInformation(out mandatoryLabel, AccessToken.TOKEN_INFORMATION_CLASS.TokenIntegrityLevel);
Marshal.FreeHGlobal(mandatoryLabelPtr);
integritySid = new Sid(mandatoryLabel.Label.Sid);
// Get Token Statistics
TOKEN_STATISTICS tokenStats;
IntPtr tokenStatsPtr = accessToken.GetTokenInformation(out tokenStats, AccessToken.TOKEN_INFORMATION_CLASS.TokenStatistics);
Marshal.FreeHGlobal(tokenStatsPtr);
SessionInfo sessionInfo = GetSessionDataForLogonSession(tokenStats.AuthenticationId);
sessionInfo.Groups = userGroups;
sessionInfo.Label = integritySid;
sessionInfo.ImpersonationLevel = tokenStats.ImpersonationLevel;
sessionInfo.TokenType = tokenStats.TokenType;
sessionInfo.Privileges = privilegeInfo;
sessionInfo.Rights = userRights;
return sessionInfo;
}
private static ArrayList GetAccountRights(IntPtr lsaHandle, IntPtr sid)
{
UInt32 res;
ArrayList rights = new ArrayList();
IntPtr userRightsPointer = IntPtr.Zero;
UInt64 countOfRights = 0;
res = LsaEnumerateAccountRights(lsaHandle, sid, out userRightsPointer, out countOfRights);
if (res != 0 && res != STATUS_OBJECT_NAME_NOT_FOUND)
throw new Win32Exception(LsaNtStatusToWinError(res), "LsaEnumerateAccountRights() failed");
else if (res != STATUS_OBJECT_NAME_NOT_FOUND)
{
LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[countOfRights];
PtrToStructureArray(userRights, userRightsPointer.ToInt64());
rights = new ArrayList();
foreach (LSA_UNICODE_STRING right in userRights)
rights.Add(Marshal.PtrToStringUni(right.buffer));
}
return rights;
}
private static SessionInfo GetSessionDataForLogonSession(LUID logonSession)
{
uint res;
UInt64 count = 0;
IntPtr luidPtr = IntPtr.Zero;
SessionInfo sessionInfo = null;
UInt64 processDataId = ConvertLuidToUint(logonSession);
res = LsaEnumerateLogonSessions(out count, out luidPtr);
if (res != 0)
throw new Win32Exception(LsaNtStatusToWinError(res), "LsaEnumerateLogonSessions() failed");
Int64 luidAddr = luidPtr.ToInt64();
try
{
for (UInt64 i = 0; i < count; i++)
{
IntPtr dataPointer = IntPtr.Zero;
res = LsaGetLogonSessionData(luidPtr, out dataPointer);
if (res == STATUS_ACCESS_DENIED) // Non admins won't be able to get info for session's that are not their own
{
luidPtr = new IntPtr(luidPtr.ToInt64() + Marshal.SizeOf(typeof(LUID)));
continue;
}
else if (res != 0)
throw new Win32Exception(LsaNtStatusToWinError(res), String.Format("LsaGetLogonSessionData() failed {0}", res));
SECURITY_LOGON_SESSION_DATA sessionData = (SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(dataPointer, typeof(SECURITY_LOGON_SESSION_DATA));
UInt64 sessionDataid = ConvertLuidToUint(sessionData.LogonId);
if (sessionDataid == processDataId)
{
ArrayList userFlags = new ArrayList();
UserFlags flags = (UserFlags)sessionData.UserFlags;
foreach (UserFlags flag in Enum.GetValues(typeof(UserFlags)))
{
if (flags.HasFlag(flag))
{
string flagName = flag.ToString().Substring(6);
flagName = flagName.Replace('_', ' ');
flagName = flagName.First().ToString().ToUpper() + flagName.Substring(1).ToLower();
userFlags.Add(flagName);
}
}
sessionInfo = new SessionInfo()
{
AuthenticationPackage = Marshal.PtrToStringUni(sessionData.AuthenticationPackage.buffer),
DnsDomainName = Marshal.PtrToStringUni(sessionData.DnsDomainName.buffer),
LoginDomain = Marshal.PtrToStringUni(sessionData.LoginDomain.buffer),
LoginTime = ConvertIntegerToDateString(sessionData.LoginTime),
LogonId = ConvertLuidToUint(sessionData.LogonId),
LogonServer = Marshal.PtrToStringUni(sessionData.LogonServer.buffer),
LogonType = sessionData.LogonType,
Upn = Marshal.PtrToStringUni(sessionData.Upn.buffer),
UserFlags = userFlags,
Account = new Sid(sessionData.Sid)
};
break;
}
luidPtr = new IntPtr(luidPtr.ToInt64() + Marshal.SizeOf(typeof(LUID)));
}
}
finally
{
LsaFreeReturnBuffer(new IntPtr(luidAddr));
}
if (sessionInfo == null)
throw new Exception(String.Format("Could not find the data for logon session {0}", processDataId));
return sessionInfo;
}
private static string ConvertIntegerToDateString(UInt64 time)
{
if (time == 0)
return null;
if (time > (UInt64)DateTime.MaxValue.ToFileTime())
return null;
DateTime dateTime = DateTime.FromFileTime((long)time);
return dateTime.ToString("o");
}
private static UInt64 ConvertLuidToUint(LUID luid)
{
UInt32 low = luid.LowPart;
UInt64 high = (UInt64)luid.HighPart;
high = high << 32;
UInt64 uintValue = (high | (UInt64)low);
return uintValue;
}
private static void PtrToStructureArray<T>(T[] array, Int64 pointerAddress)
{
Int64 pointerOffset = pointerAddress;
for (int i = 0; i < array.Length; i++, pointerOffset += Marshal.SizeOf(typeof(T)))
array[i] = (T)Marshal.PtrToStructure(new IntPtr(pointerOffset), typeof(T));
}
public static IEnumerable<T> GetValues<T>()
{
return Enum.GetValues(typeof(T)).Cast<T>();
}
}
}
'@
$original_tmp = $env:TMP
$env:TMP = $_remote_tmp
Add-Type -TypeDefinition $session_util
$env:TMP = $original_tmp
$session_info = [Ansible.SessionUtil]::GetSessionInfo()
Function Convert-Value($value) {
$new_value = $value
if ($value -is [System.Collections.ArrayList]) {
$new_value = [System.Collections.ArrayList]@()
foreach ($list_value in $value) {
$new_list_value = Convert-Value -value $list_value
[void]$new_value.Add($new_list_value)
}
} elseif ($value -is [Hashtable]) {
$new_value = @{}
foreach ($entry in $value.GetEnumerator()) {
$entry_value = Convert-Value -value $entry.Value
# manually convert Sid type entry to remove the SidType prefix
if ($entry.Name -eq "type") {
$entry_value = $entry_value.Replace("SidType", "")
}
$new_value[$entry.Name] = $entry_value
}
} elseif ($value -is [Ansible.Sid]) {
$new_value = @{
sid = $value.SidString
account_name = $value.AccountName
domain_name = $value.DomainName
type = $value.SidType.ToString().Replace("SidType", "")
}
} elseif ($value -is [Enum]) {
$new_value = $value.ToString()
}
return ,$new_value
}
$result = @{
changed = $false
}
$properties = [type][Ansible.SessionInfo]
foreach ($property in $properties.DeclaredProperties) {
$property_name = $property.Name
$property_value = $session_info.$property_name
$snake_name = Convert-StringToSnakeCase -string $property_name
$result.$snake_name = Convert-Value -value $property_value
}
Exit-Json -obj $result

@ -1,203 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_whoami
version_added: "2.5"
short_description: Get information about the current user and process
description:
- Designed to return the same information as the C(whoami /all) command.
- Also includes information missing from C(whoami) such as logon metadata like
logon rights, id, type.
notes:
- If running this module with a non admin user, the logon rights will be an
empty list as Administrator rights are required to query LSA for the
information.
seealso:
- module: win_credential
- module: win_group_membership
- module: win_user_right
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Get whoami information
win_whoami:
'''
RETURN = r'''
authentication_package:
description: The name of the authentication package used to authenticate the
user in the session.
returned: success
type: str
sample: Negotiate
user_flags:
description: The user flags for the logon session, see UserFlags in
U(https://msdn.microsoft.com/en-us/library/windows/desktop/aa380128).
returned: success
type: str
sample: Winlogon
upn:
description: The user principal name of the current user.
returned: success
type: str
sample: Administrator@DOMAIN.COM
logon_type:
description: The logon type that identifies the logon method, see
U(https://msdn.microsoft.com/en-us/library/windows/desktop/aa380129.aspx).
returned: success
type: str
sample: Network
privileges:
description: A dictionary of privileges and their state on the logon token.
returned: success
type: dict
sample: {
"SeChangeNotifyPrivileges": "enabled-by-default",
"SeRemoteShutdownPrivilege": "disabled",
"SeDebugPrivilege": "enabled"
}
label:
description: The mandatory label set to the logon session.
returned: success
type: complex
contains:
domain_name:
description: The domain name of the label SID.
returned: success
type: str
sample: Mandatory Label
sid:
description: The SID in string form.
returned: success
type: str
sample: S-1-16-12288
account_name:
description: The account name of the label SID.
returned: success
type: str
sample: High Mandatory Level
type:
description: The type of SID.
returned: success
type: str
sample: Label
impersonation_level:
description: The impersonation level of the token, only valid if
C(token_type) is C(TokenImpersonation), see
U(https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572.aspx).
returned: success
type: str
sample: SecurityAnonymous
login_time:
description: The logon time in ISO 8601 format
returned: success
type: str
sample: '2017-11-27T06:24:14.3321665+10:00'
groups:
description: A list of groups and attributes that the user is a member of.
returned: success
type: list
sample: [
{
"account_name": "Domain Users",
"domain_name": "DOMAIN",
"attributes": [
"Mandatory",
"Enabled by default",
"Enabled"
],
"sid": "S-1-5-21-1654078763-769949647-2968445802-513",
"type": "Group"
},
{
"account_name": "Administrators",
"domain_name": "BUILTIN",
"attributes": [
"Mandatory",
"Enabled by default",
"Enabled",
"Owner"
],
"sid": "S-1-5-32-544",
"type": "Alias"
}
]
account:
description: The running account SID details.
returned: success
type: complex
contains:
domain_name:
description: The domain name of the account SID.
returned: success
type: str
sample: DOMAIN
sid:
description: The SID in string form.
returned: success
type: str
sample: S-1-5-21-1654078763-769949647-2968445802-500
account_name:
description: The account name of the account SID.
returned: success
type: str
sample: Administrator
type:
description: The type of SID.
returned: success
type: str
sample: User
login_domain:
description: The name of the domain used to authenticate the owner of the
session.
returned: success
type: str
sample: DOMAIN
rights:
description: A list of logon rights assigned to the logon.
returned: success and running user is a member of the local Administrators group
type: list
sample: [
"SeNetworkLogonRight",
"SeInteractiveLogonRight",
"SeBatchLogonRight",
"SeRemoteInteractiveLogonRight"
]
logon_server:
description: The name of the server used to authenticate the owner of the
logon session.
returned: success
type: str
sample: DC01
logon_id:
description: The unique identifier of the logon session.
returned: success
type: int
sample: 20470143
dns_domain_name:
description: The DNS name of the logon session, this is an empty string if
this is not set.
returned: success
type: str
sample: DOMAIN.COM
token_type:
description: The token type to indicate whether it is a primary or
impersonation token.
returned: success
type: str
sample: TokenPrimary
'''

@ -1,522 +0,0 @@
# This file is part of Ansible
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import base64
import json
import os
import os.path
import shutil
import tempfile
import traceback
import zipfile
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleFileNotFound
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
from ansible.utils.hashing import checksum
def _walk_dirs(topdir, loader, decrypt=True, base_path=None, local_follow=False, trailing_slash_detector=None, checksum_check=False):
"""
Walk a filesystem tree returning enough information to copy the files.
This is similar to the _walk_dirs function in ``copy.py`` but returns
a dict instead of a tuple for each entry and includes the checksum of
a local file if wanted.
:arg topdir: The directory that the filesystem tree is rooted at
:arg loader: The self._loader object from ActionBase
:kwarg decrypt: Whether to decrypt a file encrypted with ansible-vault
:kwarg base_path: The initial directory structure to strip off of the
files for the destination directory. If this is None (the default),
the base_path is set to ``top_dir``.
:kwarg local_follow: Whether to follow symlinks on the source. When set
to False, no symlinks are dereferenced. When set to True (the
default), the code will dereference most symlinks. However, symlinks
can still be present if needed to break a circular link.
:kwarg trailing_slash_detector: Function to determine if a path has
a trailing directory separator. Only needed when dealing with paths on
a remote machine (in which case, pass in a function that is aware of the
directory separator conventions on the remote machine).
:kawrg whether to get the checksum of the local file and add to the dict
:returns: dictionary of dictionaries. All of the path elements in the structure are text string.
This separates all the files, directories, and symlinks along with
import information about each::
{
'files'; [{
src: '/absolute/path/to/copy/from',
dest: 'relative/path/to/copy/to',
checksum: 'b54ba7f5621240d403f06815f7246006ef8c7d43'
}, ...],
'directories'; [{
src: '/absolute/path/to/copy/from',
dest: 'relative/path/to/copy/to'
}, ...],
'symlinks'; [{
src: '/symlink/target/path',
dest: 'relative/path/to/copy/to'
}, ...],
}
The ``symlinks`` field is only populated if ``local_follow`` is set to False
*or* a circular symlink cannot be dereferenced. The ``checksum`` entry is set
to None if checksum_check=False.
"""
# Convert the path segments into byte strings
r_files = {'files': [], 'directories': [], 'symlinks': []}
def _recurse(topdir, rel_offset, parent_dirs, rel_base=u'', checksum_check=False):
"""
This is a closure (function utilizing variables from it's parent
function's scope) so that we only need one copy of all the containers.
Note that this function uses side effects (See the Variables used from
outer scope).
:arg topdir: The directory we are walking for files
:arg rel_offset: Integer defining how many characters to strip off of
the beginning of a path
:arg parent_dirs: Directories that we're copying that this directory is in.
:kwarg rel_base: String to prepend to the path after ``rel_offset`` is
applied to form the relative path.
Variables used from the outer scope
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:r_files: Dictionary of files in the hierarchy. See the return value
for :func:`walk` for the structure of this dictionary.
:local_follow: Read-only inside of :func:`_recurse`. Whether to follow symlinks
"""
for base_path, sub_folders, files in os.walk(topdir):
for filename in files:
filepath = os.path.join(base_path, filename)
dest_filepath = os.path.join(rel_base, filepath[rel_offset:])
if os.path.islink(filepath):
# Dereference the symlnk
real_file = loader.get_real_file(os.path.realpath(filepath), decrypt=decrypt)
if local_follow and os.path.isfile(real_file):
# Add the file pointed to by the symlink
r_files['files'].append(
{
"src": real_file,
"dest": dest_filepath,
"checksum": _get_local_checksum(checksum_check, real_file)
}
)
else:
# Mark this file as a symlink to copy
r_files['symlinks'].append({"src": os.readlink(filepath), "dest": dest_filepath})
else:
# Just a normal file
real_file = loader.get_real_file(filepath, decrypt=decrypt)
r_files['files'].append(
{
"src": real_file,
"dest": dest_filepath,
"checksum": _get_local_checksum(checksum_check, real_file)
}
)
for dirname in sub_folders:
dirpath = os.path.join(base_path, dirname)
dest_dirpath = os.path.join(rel_base, dirpath[rel_offset:])
real_dir = os.path.realpath(dirpath)
dir_stats = os.stat(real_dir)
if os.path.islink(dirpath):
if local_follow:
if (dir_stats.st_dev, dir_stats.st_ino) in parent_dirs:
# Just insert the symlink if the target directory
# exists inside of the copy already
r_files['symlinks'].append({"src": os.readlink(dirpath), "dest": dest_dirpath})
else:
# Walk the dirpath to find all parent directories.
new_parents = set()
parent_dir_list = os.path.dirname(dirpath).split(os.path.sep)
for parent in range(len(parent_dir_list), 0, -1):
parent_stat = os.stat(u'/'.join(parent_dir_list[:parent]))
if (parent_stat.st_dev, parent_stat.st_ino) in parent_dirs:
# Reached the point at which the directory
# tree is already known. Don't add any
# more or we might go to an ancestor that
# isn't being copied.
break
new_parents.add((parent_stat.st_dev, parent_stat.st_ino))
if (dir_stats.st_dev, dir_stats.st_ino) in new_parents:
# This was a a circular symlink. So add it as
# a symlink
r_files['symlinks'].append({"src": os.readlink(dirpath), "dest": dest_dirpath})
else:
# Walk the directory pointed to by the symlink
r_files['directories'].append({"src": real_dir, "dest": dest_dirpath})
offset = len(real_dir) + 1
_recurse(real_dir, offset, parent_dirs.union(new_parents),
rel_base=dest_dirpath,
checksum_check=checksum_check)
else:
# Add the symlink to the destination
r_files['symlinks'].append({"src": os.readlink(dirpath), "dest": dest_dirpath})
else:
# Just a normal directory
r_files['directories'].append({"src": dirpath, "dest": dest_dirpath})
# Check if the source ends with a "/" so that we know which directory
# level to work at (similar to rsync)
source_trailing_slash = False
if trailing_slash_detector:
source_trailing_slash = trailing_slash_detector(topdir)
else:
source_trailing_slash = topdir.endswith(os.path.sep)
# Calculate the offset needed to strip the base_path to make relative
# paths
if base_path is None:
base_path = topdir
if not source_trailing_slash:
base_path = os.path.dirname(base_path)
if topdir.startswith(base_path):
offset = len(base_path)
# Make sure we're making the new paths relative
if trailing_slash_detector and not trailing_slash_detector(base_path):
offset += 1
elif not base_path.endswith(os.path.sep):
offset += 1
if os.path.islink(topdir) and not local_follow:
r_files['symlinks'] = {"src": os.readlink(topdir), "dest": os.path.basename(topdir)}
return r_files
dir_stats = os.stat(topdir)
parents = frozenset(((dir_stats.st_dev, dir_stats.st_ino),))
# Actually walk the directory hierarchy
_recurse(topdir, offset, parents, checksum_check=checksum_check)
return r_files
def _get_local_checksum(get_checksum, local_path):
if get_checksum:
return checksum(local_path)
else:
return None
class ActionModule(ActionBase):
WIN_PATH_SEPARATOR = "\\"
def _create_content_tempfile(self, content):
''' Create a tempfile containing defined content '''
fd, content_tempfile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP)
f = os.fdopen(fd, 'wb')
content = to_bytes(content)
try:
f.write(content)
except Exception as err:
os.remove(content_tempfile)
raise Exception(err)
finally:
f.close()
return content_tempfile
def _create_zip_tempfile(self, files, directories):
tmpdir = tempfile.mkdtemp(dir=C.DEFAULT_LOCAL_TMP)
zip_file_path = os.path.join(tmpdir, "win_copy.zip")
zip_file = zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_STORED, True)
# encoding the file/dir name with base64 so Windows can unzip a unicode
# filename and get the right name, Windows doesn't handle unicode names
# very well
for directory in directories:
directory_path = to_bytes(directory['src'], errors='surrogate_or_strict')
archive_path = to_bytes(directory['dest'], errors='surrogate_or_strict')
encoded_path = to_text(base64.b64encode(archive_path), errors='surrogate_or_strict')
zip_file.write(directory_path, encoded_path, zipfile.ZIP_DEFLATED)
for file in files:
file_path = to_bytes(file['src'], errors='surrogate_or_strict')
archive_path = to_bytes(file['dest'], errors='surrogate_or_strict')
encoded_path = to_text(base64.b64encode(archive_path), errors='surrogate_or_strict')
zip_file.write(file_path, encoded_path, zipfile.ZIP_DEFLATED)
return zip_file_path
def _remove_tempfile_if_content_defined(self, content, content_tempfile):
if content is not None:
os.remove(content_tempfile)
def _copy_single_file(self, local_file, dest, source_rel, task_vars, tmp, backup):
if self._play_context.check_mode:
module_return = dict(changed=True)
return module_return
# copy the file across to the server
tmp_src = self._connection._shell.join_path(tmp, 'source')
self._transfer_file(local_file, tmp_src)
copy_args = self._task.args.copy()
copy_args.update(
dict(
dest=dest,
src=tmp_src,
_original_basename=source_rel,
_copy_mode="single",
backup=backup,
)
)
copy_args.pop('content', None)
copy_result = self._execute_module(module_name="copy",
module_args=copy_args,
task_vars=task_vars)
return copy_result
def _copy_zip_file(self, dest, files, directories, task_vars, tmp, backup):
# create local zip file containing all the files and directories that
# need to be copied to the server
if self._play_context.check_mode:
module_return = dict(changed=True)
return module_return
try:
zip_file = self._create_zip_tempfile(files, directories)
except Exception as e:
module_return = dict(
changed=False,
failed=True,
msg="failed to create tmp zip file: %s" % to_text(e),
exception=traceback.format_exc()
)
return module_return
zip_path = self._loader.get_real_file(zip_file)
# send zip file to remote, file must end in .zip so
# Com Shell.Application works
tmp_src = self._connection._shell.join_path(tmp, 'source.zip')
self._transfer_file(zip_path, tmp_src)
# run the explode operation of win_copy on remote
copy_args = self._task.args.copy()
copy_args.update(
dict(
src=tmp_src,
dest=dest,
_copy_mode="explode",
backup=backup,
)
)
copy_args.pop('content', None)
module_return = self._execute_module(module_name='copy',
module_args=copy_args,
task_vars=task_vars)
shutil.rmtree(os.path.dirname(zip_path))
return module_return
def run(self, tmp=None, task_vars=None):
''' handler for file transfer operations '''
if task_vars is None:
task_vars = dict()
result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect
source = self._task.args.get('src', None)
content = self._task.args.get('content', None)
dest = self._task.args.get('dest', None)
remote_src = boolean(self._task.args.get('remote_src', False), strict=False)
local_follow = boolean(self._task.args.get('local_follow', False), strict=False)
force = boolean(self._task.args.get('force', True), strict=False)
decrypt = boolean(self._task.args.get('decrypt', True), strict=False)
backup = boolean(self._task.args.get('backup', False), strict=False)
result['src'] = source
result['dest'] = dest
result['failed'] = True
if (source is None and content is None) or dest is None:
result['msg'] = "src (or content) and dest are required"
elif source is not None and content is not None:
result['msg'] = "src and content are mutually exclusive"
elif content is not None and dest is not None and (
dest.endswith(os.path.sep) or dest.endswith(self.WIN_PATH_SEPARATOR)):
result['msg'] = "dest must be a file if content is defined"
else:
del result['failed']
if result.get('failed'):
return result
# If content is defined make a temp file and write the content into it
content_tempfile = None
if content is not None:
try:
# if content comes to us as a dict it should be decoded json.
# We need to encode it back into a string and write it out
if isinstance(content, dict) or isinstance(content, list):
content_tempfile = self._create_content_tempfile(json.dumps(content))
else:
content_tempfile = self._create_content_tempfile(content)
source = content_tempfile
except Exception as err:
result['failed'] = True
result['msg'] = "could not write content tmp file: %s" % to_native(err)
return result
# all actions should occur on the remote server, run win_copy module
elif remote_src:
new_module_args = self._task.args.copy()
new_module_args.update(
dict(
_copy_mode="remote",
dest=dest,
src=source,
force=force,
backup=backup,
)
)
new_module_args.pop('content', None)
result.update(self._execute_module(module_args=new_module_args, task_vars=task_vars))
return result
# find_needle returns a path that may not have a trailing slash on a
# directory so we need to find that out first and append at the end
else:
trailing_slash = source.endswith(os.path.sep)
try:
# find in expected paths
source = self._find_needle('files', source)
except AnsibleError as e:
result['failed'] = True
result['msg'] = to_text(e)
result['exception'] = traceback.format_exc()
return result
if trailing_slash != source.endswith(os.path.sep):
if source[-1] == os.path.sep:
source = source[:-1]
else:
source = source + os.path.sep
# A list of source file tuples (full_path, relative_path) which will try to copy to the destination
source_files = {'files': [], 'directories': [], 'symlinks': []}
# If source is a directory populate our list else source is a file and translate it to a tuple.
if os.path.isdir(to_bytes(source, errors='surrogate_or_strict')):
result['operation'] = 'folder_copy'
# Get a list of the files we want to replicate on the remote side
source_files = _walk_dirs(source, self._loader, decrypt=decrypt, local_follow=local_follow,
trailing_slash_detector=self._connection._shell.path_has_trailing_slash,
checksum_check=force)
# If it's recursive copy, destination is always a dir,
# explicitly mark it so (note - win_copy module relies on this).
if not self._connection._shell.path_has_trailing_slash(dest):
dest = "%s%s" % (dest, self.WIN_PATH_SEPARATOR)
check_dest = dest
# Source is a file, add details to source_files dict
else:
result['operation'] = 'file_copy'
# If the local file does not exist, get_real_file() raises AnsibleFileNotFound
try:
source_full = self._loader.get_real_file(source, decrypt=decrypt)
except AnsibleFileNotFound as e:
result['failed'] = True
result['msg'] = "could not find src=%s, %s" % (source_full, to_text(e))
return result
original_basename = os.path.basename(source)
result['original_basename'] = original_basename
# check if dest ends with / or \ and append source filename to dest
if self._connection._shell.path_has_trailing_slash(dest):
check_dest = dest
filename = original_basename
result['dest'] = self._connection._shell.join_path(dest, filename)
else:
# replace \\ with / so we can use os.path to get the filename or dirname
unix_path = dest.replace(self.WIN_PATH_SEPARATOR, os.path.sep)
filename = os.path.basename(unix_path)
check_dest = os.path.dirname(unix_path)
file_checksum = _get_local_checksum(force, source_full)
source_files['files'].append(
dict(
src=source_full,
dest=filename,
checksum=file_checksum
)
)
result['checksum'] = file_checksum
result['size'] = os.path.getsize(to_bytes(source_full, errors='surrogate_or_strict'))
# find out the files/directories/symlinks that we need to copy to the server
query_args = self._task.args.copy()
query_args.update(
dict(
_copy_mode="query",
dest=check_dest,
force=force,
files=source_files['files'],
directories=source_files['directories'],
symlinks=source_files['symlinks'],
)
)
# src is not required for query, will fail path validation is src has unix allowed chars
query_args.pop('src', None)
query_args.pop('content', None)
query_return = self._execute_module(module_args=query_args,
task_vars=task_vars)
if query_return.get('failed') is True:
result.update(query_return)
return result
if len(query_return['files']) > 0 or len(query_return['directories']) > 0 and self._connection._shell.tmpdir is None:
self._connection._shell.tmpdir = self._make_tmp_path()
if len(query_return['files']) == 1 and len(query_return['directories']) == 0:
# we only need to copy 1 file, don't mess around with zips
file_src = query_return['files'][0]['src']
file_dest = query_return['files'][0]['dest']
result.update(self._copy_single_file(file_src, dest, file_dest,
task_vars, self._connection._shell.tmpdir, backup))
if result.get('failed') is True:
result['msg'] = "failed to copy file %s: %s" % (file_src, result['msg'])
result['changed'] = True
elif len(query_return['files']) > 0 or len(query_return['directories']) > 0:
# either multiple files or directories need to be copied, compress
# to a zip and 'explode' the zip on the server
# TODO: handle symlinks
result.update(self._copy_zip_file(dest, source_files['files'],
source_files['directories'],
task_vars, self._connection._shell.tmpdir, backup))
result['changed'] = True
else:
# no operations need to occur
result['failed'] = False
result['changed'] = False
# remove the content tmp file and remote tmp file if it was created
self._remove_tempfile_if_content_defined(content, content_tempfile)
self._remove_tmp_path(self._connection._shell.tmpdir)
return result

@ -1,96 +0,0 @@
# Copyright: (c) 2018, Matt Davis <mdavis@ansible.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from datetime import datetime
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native
from ansible.plugins.action import ActionBase
from ansible.plugins.action.reboot import ActionModule as RebootActionModule
from ansible.utils.display import Display
display = Display()
class TimedOutException(Exception):
pass
class ActionModule(RebootActionModule, ActionBase):
TRANSFERS_FILES = False
_VALID_ARGS = frozenset((
'connect_timeout', 'connect_timeout_sec', 'msg', 'post_reboot_delay', 'post_reboot_delay_sec', 'pre_reboot_delay', 'pre_reboot_delay_sec',
'reboot_timeout', 'reboot_timeout_sec', 'shutdown_timeout', 'shutdown_timeout_sec', 'test_command',
))
DEFAULT_BOOT_TIME_COMMAND = "(Get-WmiObject -ClassName Win32_OperatingSystem).LastBootUpTime"
DEFAULT_CONNECT_TIMEOUT = 5
DEFAULT_PRE_REBOOT_DELAY = 2
DEFAULT_SUDOABLE = False
DEFAULT_SHUTDOWN_COMMAND_ARGS = '/r /t {delay_sec} /c "{message}"'
DEPRECATED_ARGS = {
'shutdown_timeout': '2.5',
'shutdown_timeout_sec': '2.5',
}
def __init__(self, *args, **kwargs):
super(ActionModule, self).__init__(*args, **kwargs)
def get_distribution(self, task_vars):
return {'name': 'windows', 'version': '', 'family': ''}
def get_shutdown_command(self, task_vars, distribution):
return self.DEFAULT_SHUTDOWN_COMMAND
def run_test_command(self, distribution, **kwargs):
# Need to wrap the test_command in our PowerShell encoded wrapper. This is done to align the command input to a
# common shell and to allow the psrp connection plugin to report the correct exit code without manually setting
# $LASTEXITCODE for just that plugin.
test_command = self._task.args.get('test_command', self.DEFAULT_TEST_COMMAND)
kwargs['test_command'] = self._connection._shell._encode_script(test_command)
super(ActionModule, self).run_test_command(distribution, **kwargs)
def perform_reboot(self, task_vars, distribution):
shutdown_command = self.get_shutdown_command(task_vars, distribution)
shutdown_command_args = self.get_shutdown_command_args(distribution)
reboot_command = self._connection._shell._encode_script('{0} {1}'.format(shutdown_command, shutdown_command_args))
display.vvv("{action}: rebooting server...".format(action=self._task.action))
display.debug("{action}: distribution: {dist}".format(action=self._task.action, dist=distribution))
display.debug("{action}: rebooting server with command '{command}'".format(action=self._task.action, command=reboot_command))
result = {}
reboot_result = self._low_level_execute_command(reboot_command, sudoable=self.DEFAULT_SUDOABLE)
result['start'] = datetime.utcnow()
# Test for "A system shutdown has already been scheduled. (1190)" and handle it gracefully
stdout = reboot_result['stdout']
stderr = reboot_result['stderr']
if reboot_result['rc'] == 1190 or (reboot_result['rc'] != 0 and "(1190)" in reboot_result['stderr']):
display.warning('A scheduled reboot was pre-empted by Ansible.')
# Try to abort (this may fail if it was already aborted)
result1 = self._low_level_execute_command(self._connection._shell._encode_script('shutdown /a'),
sudoable=self.DEFAULT_SUDOABLE)
# Initiate reboot again
result2 = self._low_level_execute_command(reboot_command, sudoable=self.DEFAULT_SUDOABLE)
reboot_result['rc'] = result2['rc']
stdout += result1['stdout'] + result2['stdout']
stderr += result1['stderr'] + result2['stderr']
if reboot_result['rc'] != 0:
result['failed'] = True
result['rebooted'] = False
result['msg'] = "Reboot command failed, error was: {stdout} {stderr}".format(
stdout=to_native(stdout.strip()),
stderr=to_native(stderr.strip()))
return result
result['failed'] = False
return result

@ -1,29 +0,0 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.plugins.action import ActionBase
from ansible.plugins.action.template import ActionModule as TemplateActionModule
# Even though TemplateActionModule inherits from ActionBase, we still need to
# directly inherit from ActionBase to appease the plugin loader.
class ActionModule(TemplateActionModule, ActionBase):
DEFAULT_NEWLINE_SEQUENCE = '\r\n'

@ -1,247 +0,0 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.parsing.yaml.objects import AnsibleUnicode
from ansible.plugins.action import ActionBase
from ansible.plugins.loader import become_loader
from ansible.utils.display import Display
display = Display()
class ActionModule(ActionBase):
DEFAULT_REBOOT_TIMEOUT = 1200
def _run_win_updates(self, module_args, task_vars, use_task):
display.vvv("win_updates: running win_updates module")
wrap_async = self._task.async_val
result = self._execute_module_with_become(module_name='win_updates',
module_args=module_args,
task_vars=task_vars,
wrap_async=wrap_async,
use_task=use_task)
return result
def _reboot_server(self, task_vars, reboot_timeout, use_task):
display.vvv("win_updates: rebooting remote host after update install")
reboot_args = {
'reboot_timeout': reboot_timeout
}
reboot_result = self._run_action_plugin('win_reboot', task_vars,
module_args=reboot_args)
if reboot_result.get('failed', False):
raise AnsibleError(reboot_result['msg'])
# only run this if the user has specified we can only use scheduled
# tasks, the win_shell command requires become and will be skipped if
# become isn't available to use
if use_task:
display.vvv("win_updates: skipping WUA is not busy check as "
"use_scheduled_task=True is set")
else:
display.vvv("win_updates: checking WUA is not busy with win_shell "
"command")
# While this always returns False after a reboot it doesn't return
# a value until Windows is actually ready and finished installing
# updates. This needs to run with become as WUA doesn't work over
# WinRM, ignore connection errors as another reboot can happen
command = "(New-Object -ComObject Microsoft.Update.Session)." \
"CreateUpdateInstaller().IsBusy"
shell_module_args = {
'_raw_params': command
}
try:
shell_result = self._execute_module_with_become(
module_name='win_shell', module_args=shell_module_args,
task_vars=task_vars, wrap_async=False, use_task=use_task
)
display.vvv("win_updates: shell wait results: %s"
% json.dumps(shell_result))
except Exception as exc:
display.debug("win_updates: Fatal error when running shell "
"command, attempting to recover: %s" % to_text(exc))
display.vvv("win_updates: ensure the connection is up and running")
# in case Windows needs to reboot again after the updates, we wait for
# the connection to be stable again
wait_for_result = self._run_action_plugin('wait_for_connection',
task_vars)
if wait_for_result.get('failed', False):
raise AnsibleError(wait_for_result['msg'])
def _run_action_plugin(self, plugin_name, task_vars, module_args=None):
# Create new task object and reset the args
new_task = self._task.copy()
new_task.args = {}
if module_args is not None:
for key, value in module_args.items():
new_task.args[key] = value
# run the action plugin and return the results
action = self._shared_loader_obj.action_loader.get(
plugin_name,
task=new_task,
connection=self._connection,
play_context=self._play_context,
loader=self._loader,
templar=self._templar,
shared_loader_obj=self._shared_loader_obj
)
return action.run(task_vars=task_vars)
def _merge_dict(self, original, new):
dict_var = original.copy()
dict_var.update(new)
return dict_var
def _execute_module_with_become(self, module_name, module_args, task_vars,
wrap_async, use_task):
orig_become = self._connection.become
try:
if not use_task and orig_become is None:
become = become_loader.get('runas')
become.set_options(direct={'become_user': 'SYSTEM', 'become_pass': None})
self._connection.set_become_plugin(become)
module_res = self._execute_module(module_name=module_name,
module_args=module_args,
task_vars=task_vars,
wrap_async=wrap_async)
finally:
self._connection.set_become_plugin(orig_become)
return module_res
def run(self, tmp=None, task_vars=None):
self._supports_check_mode = True
self._supports_async = True
result = super(ActionModule, self).run(tmp, task_vars)
del tmp # tmp no longer has any effect
state = self._task.args.get('state', 'installed')
reboot = self._task.args.get('reboot', False)
reboot_timeout = self._task.args.get('reboot_timeout',
self.DEFAULT_REBOOT_TIMEOUT)
use_task = boolean(self._task.args.get('use_scheduled_task', False),
strict=False)
if state not in ['installed', 'searched', 'downloaded']:
result['failed'] = True
result['msg'] = "state must be either installed, searched or downloaded"
return result
try:
reboot = boolean(reboot)
except TypeError as exc:
result['failed'] = True
result['msg'] = "cannot parse reboot as a boolean: %s" % to_text(exc)
return result
if not isinstance(reboot_timeout, int):
result['failed'] = True
result['msg'] = "reboot_timeout must be an integer"
return result
if reboot and self._task.async_val > 0:
result['failed'] = True
result['msg'] = "async is not supported for this task when " \
"reboot=yes"
return result
# Run the module
new_module_args = self._task.args.copy()
new_module_args.pop('reboot', None)
new_module_args.pop('reboot_timeout', None)
result = self._run_win_updates(new_module_args, task_vars, use_task)
# if the module failed to run at all then changed won't be populated
# so we just return the result as is
# https://github.com/ansible/ansible/issues/38232
failed = result.get('failed', False)
if ("updates" not in result.keys() and self._task.async_val == 0) or failed:
result['failed'] = True
return result
changed = result.get('changed', False)
updates = result.get('updates', dict())
filtered_updates = result.get('filtered_updates', dict())
found_update_count = result.get('found_update_count', 0)
installed_update_count = result.get('installed_update_count', 0)
# Handle automatic reboots if the reboot flag is set
if reboot and state == 'installed' and not \
self._play_context.check_mode:
previously_errored = False
while result['installed_update_count'] > 0 or \
result['found_update_count'] > 0 or \
result['reboot_required'] is True:
display.vvv("win_updates: check win_updates results for "
"automatic reboot: %s" % json.dumps(result))
# check if the module failed, break from the loop if it
# previously failed and return error to the user
if result.get('failed', False):
if previously_errored:
break
previously_errored = True
else:
previously_errored = False
reboot_error = None
# check if a reboot was required before installing the updates
if result.get('msg', '') == "A reboot is required before " \
"more updates can be installed":
reboot_error = "reboot was required before more updates " \
"can be installed"
if result.get('reboot_required', False):
if reboot_error is None:
reboot_error = "reboot was required to finalise " \
"update install"
try:
changed = True
self._reboot_server(task_vars, reboot_timeout,
use_task)
except AnsibleError as exc:
result['failed'] = True
result['msg'] = "Failed to reboot remote host when " \
"%s: %s" \
% (reboot_error, to_text(exc))
break
result.pop('msg', None)
# rerun the win_updates module after the reboot is complete
result = self._run_win_updates(new_module_args, task_vars,
use_task)
if result.get('failed', False):
return result
result_updates = result.get('updates', dict())
result_filtered_updates = result.get('filtered_updates', dict())
updates = self._merge_dict(updates, result_updates)
filtered_updates = self._merge_dict(filtered_updates,
result_filtered_updates)
found_update_count += result.get('found_update_count', 0)
installed_update_count += result.get('installed_update_count', 0)
if result['changed']:
changed = True
# finally create the return dict based on the aggregated execution
# values if we are not in async
if self._task.async_val == 0:
result['changed'] = changed
result['updates'] = updates
result['filtered_updates'] = filtered_updates
result['found_update_count'] = found_update_count
result['installed_update_count'] = installed_update_count
return result

@ -1,154 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
# Standard files documentation fragment
DOCUMENTATION = r'''
options:
method:
description:
- The HTTP Method of the request.
type: str
follow_redirects:
description:
- Whether or the module should follow redirects.
- C(all) will follow all redirect.
- C(none) will not follow any redirect.
- C(safe) will follow only "safe" redirects, where "safe" means that the
client is only doing a C(GET) or C(HEAD) on the URI to which it is being
redirected.
choices:
- all
- none
- safe
default: safe
type: str
headers:
description:
- Extra headers to set on the request.
- This should be a dictionary where the key is the header name and the
value is the value for that header.
type: dict
http_agent:
description:
- Header to identify as, generally appears in web server logs.
- This is set to the C(User-Agent) header on a HTTP request.
default: ansible-httpget
type: str
version_added: "2.9"
maximum_redirection:
description:
- Specify how many times the module will redirect a connection to an
alternative URI before the connection fails.
- If set to C(0) or I(follow_redirects) is set to C(none), or C(safe) when
not doing a C(GET) or C(HEAD) it prevents all redirection.
default: 50
type: int
timeout:
description:
- Specifies how long the request can be pending before it times out (in
seconds).
- Set to C(0) to specify an infinite timeout.
default: 30
type: int
version_added: "2.4"
validate_certs:
description:
- If C(no), SSL certificates will not be validated.
- This should only be used on personally controlled sites using self-signed
certificates.
default: yes
type: bool
version_added: "2.4"
client_cert:
description:
- The path to the client certificate (.pfx) that is used for X509
authentication. This path can either be the path to the C(pfx) on the
filesystem or the PowerShell certificate path
C(Cert:\CurrentUser\My\<thumbprint>).
- The WinRM connection must be authenticated with C(CredSSP) or C(become)
is used on the task if the certificate file is not password protected.
- Other authentication types can set I(client_cert_password) when the cert
is password protected.
type: str
client_cert_password:
description:
- The password for I(client_cert) if the cert is password protected.
type: str
force_basic_auth:
description:
- By default the authentication header is only sent when a webservice
responses to an initial request with a 401 status. Since some basic auth
services do not properly send a 401, logins will fail.
- This option forces the sending of the Basic authentication header upon
the original request.
default: no
type: bool
version_added: "2.5"
url_username:
description:
- The username to use for authentication.
type: str
url_password:
description:
- The password for I(url_username).
type: str
use_default_credential:
description:
- Uses the current user's credentials when authenticating with a server
protected with C(NTLM), C(Kerberos), or C(Negotiate) authentication.
- Sites that use C(Basic) auth will still require explicit credentials
through the I(url_username) and I(url_password) options.
- The module will only have access to the user's credentials if using
C(become) with a password, you are connecting with SSH using a password,
or connecting with WinRM using C(CredSSP) or C(Kerberos with delegation).
- If not using C(become) or a different auth method to the ones stated
above, there will be no default credentials available and no
authentication will occur.
default: no
type: bool
version_added: "2.9"
use_proxy:
description:
- If C(no), it will not use the proxy defined in IE for the current user.
default: yes
type: bool
proxy_url:
description:
- An explicit proxy to use for the request.
- By default, the request will use the IE defined proxy unless I(use_proxy)
is set to C(no).
type: str
proxy_username:
description:
- The username to use for proxy authentication.
type: str
proxy_password:
description:
- The password for I(proxy_username).
type: str
proxy_use_default_credential:
description:
- Uses the current user's credentials when authenticating with a proxy host
protected with C(NTLM), C(Kerberos), or C(Negotiate) authentication.
- Proxies that use C(Basic) auth will still require explicit credentials
through the I(proxy_username) and I(proxy_password) options.
- The module will only have access to the user's credentials if using
C(become) with a password, you are connecting with SSH using a password,
or connecting with WinRM using C(CredSSP) or C(Kerberos with delegation).
- If not using C(become) or a different auth method to the ones stated
above, there will be no default credentials available and no proxy
authentication will occur.
default: no
type: bool
version_added: "2.9"
seealso:
- module: win_inet_proxy
'''

@ -1 +0,0 @@
shippable/windows/group3

@ -1,5 +0,0 @@
---
test_acl_path: '{{ win_output_dir }}\win_acl .ÅÑŚÌβŁÈ [$!@^&test(;)]'
test_acl_network_path: \\localhost\{{ test_acl_path[0:1] }}$\{{ test_acl_path[3:] }}
# Use HKU as that path is not automatically loaded in the PSProvider making our test more complex
test_acl_reg_path: HKU:\.DEFAULT\Ansible Test .ÅÑŚÌβŁÈ [$!@^&test(;)]

@ -1,33 +0,0 @@
---
- name: ensure we start with a clean dir
win_file:
path: '{{ test_acl_path }}'
state: '{{ item }}'
with_items:
- absent
- directory
- name: ensure we start with a clean reg path
win_regedit:
path: '{{ test_acl_reg_path }}'
delete_key: yes
state: '{{ item }}'
with_items:
- absent
- present
- block:
- name: run tests
include_tasks: tests.yml
always:
- name: cleanup testing dir
win_file:
path: '{{ test_acl_path }}'
state: absent
- name: cleanup testing reg path
win_regedit:
path: '{{ test_acl_reg_path }}'
delete_key: yes
state: absent

@ -1,362 +0,0 @@
# these are very basic tests, they should be expanded greatly as this is a core module
---
- name: get register cmd that will get ace info
set_fact:
test_ace_cmd: |
# Overcome bug in Set-Acl/Get-Acl for registry paths and -LiteralPath
New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS > $null
Push-Location -LiteralPath (Split-Path -Path $path -Qualifier)
$rights_key = if ((Get-Item -LiteralPath $path -Force).PSProvider.Name -eq "Registry") {
"RegistryRights"
} else {
"FileSystemRights"
}
$ace_list = (Get-Acl -LiteralPath $path).Access | Where-Object { $_.IsInherited -eq $false } | ForEach-Object {
@{
rights = $_."$rights_key".ToString()
type = $_.AccessControlType.ToString()
identity = $_.IdentityReference.Value.ToString()
inheritance_flags = $_.InheritanceFlags.ToString()
propagation_flags = $_.PropagationFlags.ToString()
}
}
Pop-Location
ConvertTo-Json -InputObject @($ace_list)
- name: add write rights to Guest
win_acl:
path: '{{ test_acl_path }}'
type: allow
user: Guests
rights: Write
register: allow_right
- name: get result of add write rights to Guest
win_shell: '$path = ''{{ test_acl_path }}''; {{ test_ace_cmd }}'
register: allow_right_actual
- name: assert add write rights to Guest
assert:
that:
- allow_right is changed
- (allow_right_actual.stdout|from_json)|count == 1
- (allow_right_actual.stdout|from_json)[0].identity == 'BUILTIN\Guests'
- (allow_right_actual.stdout|from_json)[0].inheritance_flags == 'ContainerInherit, ObjectInherit'
- (allow_right_actual.stdout|from_json)[0].propagation_flags == 'None'
- (allow_right_actual.stdout|from_json)[0].rights == 'Write, Synchronize'
- (allow_right_actual.stdout|from_json)[0].type == 'Allow'
- name: add write rights to Guest (idempotent)
win_acl:
path: '{{ test_acl_path }}'
type: allow
user: Guests
rights: Write
register: allow_right_again
- name: assert add write rights to Guest (idempotent)
assert:
that:
- not allow_right_again is changed
- name: remove write rights from Guest
win_acl:
path: '{{ test_acl_path }}'
type: allow
user: Guests
rights: Write
state: absent
register: remove_right
- name: get result of remove write rights from Guest
win_shell: '$path = ''{{ test_acl_path }}''; {{ test_ace_cmd }}'
register: remove_right_actual
- name: assert remove write rights from Guest
assert:
that:
- remove_right is changed
- remove_right_actual.stdout_lines == ["[", "", "]"]
- name: remove write rights from Guest (idempotent)
win_acl:
path: '{{ test_acl_path }}'
type: allow
user: Guests
rights: Write
state: absent
register: remove_right_again
- name: assert remote write rights from Guest (idempotent)
assert:
that:
- not remove_right_again is changed
- name: add deny write rights to Guest
win_acl:
path: '{{ test_acl_path }}'
type: deny
user: Guests
rights: Write
inherit: ContainerInherit
propagation: NoPropagateInherit
state: present
register: add_deny_right
- name: get result of add deny write rights to Guest
win_shell: '$path = ''{{ test_acl_path }}''; {{ test_ace_cmd }}'
register: add_deny_right_actual
- name: assert add deny write rights to Guest
assert:
that:
- add_deny_right is changed
- (add_deny_right_actual.stdout|from_json)|count == 1
- (add_deny_right_actual.stdout|from_json)[0].identity == 'BUILTIN\Guests'
- (add_deny_right_actual.stdout|from_json)[0].inheritance_flags == 'ContainerInherit'
- (add_deny_right_actual.stdout|from_json)[0].propagation_flags == 'NoPropagateInherit'
- (add_deny_right_actual.stdout|from_json)[0].rights == 'Write'
- (add_deny_right_actual.stdout|from_json)[0].type == 'Deny'
- name: add deny write rights to Guest (idempotent)
win_acl:
path: '{{ test_acl_path }}'
type: deny
user: Guests
rights: Write
inherit: ContainerInherit
propagation: NoPropagateInherit
state: present
register: add_deny_right_again
- name: assert add deny write rights to Guest (idempotent)
assert:
that:
- not add_deny_right_again is changed
- name: remove deny write rights from Guest
win_acl:
path: '{{ test_acl_path }}'
type: deny
user: Guests
rights: Write
inherit: ContainerInherit
propagation: NoPropagateInherit
state: absent
register: remove_deny_right
- name: get result of remove deny write rights from Guest
win_shell: '$path = ''{{ test_acl_path }}''; {{ test_ace_cmd }}'
register: remove_deny_right_actual
- name: assert remove deny write rights from Guest
assert:
that:
- remove_deny_right is changed
- remove_deny_right_actual.stdout_lines == ["[", "", "]"]
- name: remove deny write rights from Guest (idempotent)
win_acl:
path: '{{ test_acl_path }}'
type: deny
user: Guests
rights: Write
inherit: ContainerInherit
propagation: NoPropagateInherit
state: absent
register: remove_deny_right_again
- name: assert remove deny write rights from Guest (idempotent)
assert:
that:
- not remove_deny_right_again is changed
- name: add write rights to Guest - network
win_acl:
path: '{{ test_acl_network_path }}'
type: allow
user: Guests
rights: Write
register: allow_right
- name: get result of add write rights to Guest - network
win_shell: '$path = ''{{ test_acl_path }}''; {{ test_ace_cmd }}'
register: allow_right_actual
- name: assert add write rights to Guest - network
assert:
that:
- allow_right is changed
- (allow_right_actual.stdout|from_json)|count == 1
- (allow_right_actual.stdout|from_json)[0].identity == 'BUILTIN\Guests'
- (allow_right_actual.stdout|from_json)[0].inheritance_flags == 'ContainerInherit, ObjectInherit'
- (allow_right_actual.stdout|from_json)[0].propagation_flags == 'None'
- (allow_right_actual.stdout|from_json)[0].rights == 'Write, Synchronize'
- (allow_right_actual.stdout|from_json)[0].type == 'Allow'
- name: remove write rights from Guest - network
win_acl:
path: '{{ test_acl_network_path }}'
type: allow
user: Guests
rights: Write
state: absent
register: remove_right
- name: get result of remove write rights from Guest - network
win_shell: '$path = ''{{ test_acl_path }}''; {{ test_ace_cmd }}'
register: remove_right_actual
- name: assert remove write rights from Guest
assert:
that:
- remove_right is changed
- remove_right_actual.stdout_lines == ["[", "", "]"]
- name: add write rights to Guest - registry
win_acl:
path: '{{ test_acl_reg_path }}'
type: allow
user: Guests
rights: WriteKey
register: allow_right_reg
- name: get result of add write rights to Guest - registry
win_shell: '$path = ''{{ test_acl_reg_path }}''; {{ test_ace_cmd }}'
register: allow_right_reg_actual
- name: assert add write rights to Guest - registry
assert:
that:
- allow_right_reg is changed
- (allow_right_reg_actual.stdout|from_json)|count == 1
- (allow_right_reg_actual.stdout|from_json)[0].identity == 'BUILTIN\Guests'
- (allow_right_reg_actual.stdout|from_json)[0].inheritance_flags == 'ContainerInherit, ObjectInherit'
- (allow_right_reg_actual.stdout|from_json)[0].propagation_flags == 'None'
- (allow_right_reg_actual.stdout|from_json)[0].rights == 'WriteKey'
- (allow_right_reg_actual.stdout|from_json)[0].type == 'Allow'
- name: add write rights to Guest (idempotent) - registry
win_acl:
path: '{{ test_acl_reg_path }}'
type: allow
user: Guests
rights: WriteKey
register: allow_right_reg_again
- name: assert add write rights to Guest (idempotent) - registry
assert:
that:
- not allow_right_reg_again is changed
- name: remove write rights from Guest - registry
win_acl:
path: '{{ test_acl_reg_path }}'
type: allow
user: Guests
rights: WriteKey
state: absent
register: remove_right_reg
- name: get result of remove write rights from Guest - registry
win_shell: '$path = ''{{ test_acl_reg_path }}''; {{ test_ace_cmd }}'
register: remove_right_reg_actual
- name: assert remove write rights from Guest - registry
assert:
that:
- remove_right_reg is changed
- remove_right_reg_actual.stdout_lines == ["[", "", "]"]
- name: remove write rights from Guest (idempotent) - registry
win_acl:
path: '{{ test_acl_reg_path }}'
type: allow
user: Guests
rights: WriteKey
state: absent
register: remove_right_reg_again
- name: assert remote write rights from Guest (idempotent) - registry
assert:
that:
- not remove_right_reg_again is changed
- name: add deny write rights to Guest - registry
win_acl:
path: '{{ test_acl_reg_path }}'
type: deny
user: Guests
rights: WriteKey
inherit: ContainerInherit
propagation: NoPropagateInherit
state: present
register: add_deny_right_reg
- name: get result of add deny write rights to Guest - registry
win_shell: '$path = ''{{ test_acl_reg_path }}''; {{ test_ace_cmd }}'
register: add_deny_right_reg_actual
- name: assert add deny write rights to Guest - registry
assert:
that:
- add_deny_right_reg is changed
- (add_deny_right_reg_actual.stdout|from_json)|count == 1
- (add_deny_right_reg_actual.stdout|from_json)[0].identity == 'BUILTIN\Guests'
- (add_deny_right_reg_actual.stdout|from_json)[0].inheritance_flags == 'ContainerInherit'
- (add_deny_right_reg_actual.stdout|from_json)[0].propagation_flags == 'NoPropagateInherit'
- (add_deny_right_reg_actual.stdout|from_json)[0].rights == 'WriteKey'
- (add_deny_right_reg_actual.stdout|from_json)[0].type == 'Deny'
- name: add deny write rights to Guest (idempotent) - registry
win_acl:
path: '{{ test_acl_reg_path }}'
type: deny
user: Guests
rights: WriteKey
inherit: ContainerInherit
propagation: NoPropagateInherit
state: present
register: add_deny_right_reg_again
- name: assert add deny write rights to Guest (idempotent) - registry
assert:
that:
- not add_deny_right_reg_again is changed
- name: remove deny write rights from Guest - registry
win_acl:
path: '{{ test_acl_reg_path }}'
type: deny
user: Guests
rights: WriteKey
inherit: ContainerInherit
propagation: NoPropagateInherit
state: absent
register: remove_deny_right_reg
- name: get result of remove deny write rights from Guest - registry
win_shell: '$path = ''{{ test_acl_reg_path }}''; {{ test_ace_cmd }}'
register: remove_deny_right_reg_actual
- name: assert remove deny write rights from Guest - registry
assert:
that:
- remove_deny_right_reg is changed
- remove_deny_right_reg_actual.stdout_lines == ["[", "", "]"]
- name: remove deny write rights from Guest (idempotent) - registry
win_acl:
path: '{{ test_acl_reg_path }}'
type: deny
user: Guests
rights: WriteKey
inherit: ContainerInherit
propagation: NoPropagateInherit
state: absent
register: remove_deny_right_reg_again
- name: assert remove deny write rights from Guest (idempotent) - registry
assert:
that:
- not remove_deny_right_reg_again is changed

@ -1 +0,0 @@
test_win_acl_inheritance_path: C:\ansible\win_acl_inheritance .ÅÑŚÌβŁÈ [$!@^&test(;)]

@ -1,36 +0,0 @@
#!powershell
# WANT_JSON
# POWERSHELL_COMMON
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 2.0
$params = Parse-Args $args -supports_check_mode $false
$path = Get-AnsibleParam -obj $params "path" -type "path" -failifempty $true
$result = @{
changed = $false
}
$acl = Get-Acl -LiteralPath $path
$result.inherited = $acl.AreAccessRulesProtected -eq $false
$user_details = @{}
$acl.Access | ForEach-Object {
$user = $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value
if ($user_details.ContainsKey($user)) {
$details = $user_details.$user
} else {
$details = @{
isinherited = $false
}
}
$details.isinherited = $_.IsInherited
$user_details.$user = $details
}
$result.user_details = $user_details
Exit-Json $result

@ -1,174 +0,0 @@
---
# Test setup
# Use single task to save in CI runtime
- name: create test folders
win_shell: |
$ErrorActionPreference = 'Stop'
$tmp_dir = '{{ test_win_acl_inheritance_path }}'
if (Test-Path -LiteralPath $tmp_dir) {
Remove-Item -LiteralPath $tmp_dir -Force -Recurse
}
New-Item -Path $tmp_dir -ItemType Directory > $null
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$current_sid = ([System.DirectoryServices.AccountManagement.UserPrincipal]::Current).Sid
$system_sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null)
$everyone_sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @([System.Security.Principal.WellKnownSidType]::WorldSid, $null)
$sd = New-Object -TypeName System.Security.AccessControl.DirectorySecurity
$sd.SetAccessRuleProtection($true, $false)
$sd.AddAccessRule(
(New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList @(
$system_sid,
[System.Security.AccessControl.FileSystemRights]::FullControl,
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit",
[System.Security.AccessControl.PropagationFlags]::None,
[System.Security.AccessControl.AccessControlType]::Allow
))
)
$sd.AddAccessRule(
(New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList @(
$current_sid,
[System.Security.AccessControl.FileSystemRights]::FullControl,
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit",
[System.Security.AccessControl.PropagationFlags]::None,
[System.Security.AccessControl.AccessControlType]::Allow
))
)
$sd.AddAccessRule(
(New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList @(
$everyone_sid,
[System.Security.AccessControl.FileSystemRights]::Read,
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit",
[System.Security.AccessControl.PropagationFlags]::None,
[System.Security.AccessControl.AccessControlType]::Allow
))
)
Set-Acl -LiteralPath $tmp_dir -AclObject $sd
New-Item -Path "$tmp_dir\folder" -ItemType Directory > $null
Set-Content -LiteralPath "$tmp_dir\folder\file.txt" -Value 'a'
$system_sid.Value
$current_sid.Value
$everyone_sid.Value
register: test_sids # register the output SID values used for comparison tests below
# Run tests
- name: remove inheritance check
win_acl_inheritance:
path: '{{ test_win_acl_inheritance_path }}\folder'
reorganize: True
state: absent
register: remove_check
check_mode: True
- name: get actual remove inheritance check
test_get_acl:
path: '{{ test_win_acl_inheritance_path }}\folder'
register: actual_remove_check
- name: assert remove inheritance check
assert:
that:
- remove_check is changed
- actual_remove_check.inherited == True
- actual_remove_check.user_details[test_sids.stdout_lines[0]].isinherited == True
- actual_remove_check.user_details[test_sids.stdout_lines[1]].isinherited == True
- actual_remove_check.user_details[test_sids.stdout_lines[2]].isinherited == True
- name: remove inheritance
win_acl_inheritance:
path: '{{ test_win_acl_inheritance_path }}\folder'
reorganize: True
state: absent
register: remove
- name: get actual remove inheritance
test_get_acl:
path: '{{ test_win_acl_inheritance_path }}\folder'
register: actual_remove
- name: assert remove inheritance
assert:
that:
- remove is changed
- actual_remove.inherited == False
- actual_remove.user_details[test_sids.stdout_lines[0]].isinherited == False
- actual_remove.user_details[test_sids.stdout_lines[1]].isinherited == False
- actual_remove.user_details[test_sids.stdout_lines[2]].isinherited == False
- name: remove inheritance again
win_acl_inheritance:
path: '{{ test_win_acl_inheritance_path }}\folder'
reorganize: True
state: absent
register: remove_again
- name: assert remove inheritance again
assert:
that:
- remove_again is not changed
- name: add inheritance check
win_acl_inheritance:
path: '{{ test_win_acl_inheritance_path }}\folder'
reorganize: True
state: present
register: add_check
check_mode: True
- name: get actual add inheritance check
test_get_acl:
path: '{{ test_win_acl_inheritance_path }}\folder'
register: actual_add_check
- name: assert add inheritance check
assert:
that:
- add_check is changed
- actual_add_check.inherited == False
- actual_add_check.user_details[test_sids.stdout_lines[0]].isinherited == False
- actual_add_check.user_details[test_sids.stdout_lines[1]].isinherited == False
- actual_add_check.user_details[test_sids.stdout_lines[2]].isinherited == False
- name: add inheritance
win_acl_inheritance:
path: '{{ test_win_acl_inheritance_path }}\folder'
reorganize: True
state: present
register: add
- name: get actual add inheritance
test_get_acl:
path: '{{ test_win_acl_inheritance_path }}\folder'
register: actual_add
- name: assert add inheritance
assert:
that:
- add is changed
- actual_add.inherited == True
- actual_add.user_details[test_sids.stdout_lines[0]].isinherited == True
- actual_add.user_details[test_sids.stdout_lines[1]].isinherited == True
- actual_add.user_details[test_sids.stdout_lines[2]].isinherited == True
- name: add inheritance again
win_acl_inheritance:
path: '{{ test_win_acl_inheritance_path }}\folder'
reorganize: True
state: present
register: add_again
- name: assert add inheritance again
assert:
that:
- add_again is not changed
# Test cleanup
- name: remove test folder
win_file:
path: '{{ test_win_acl_inheritance_path }}'
state: absent

@ -1,2 +0,0 @@
shippable/windows/group7
skip/windows/2016 # Host takes a while to run and module isn't OS dependent

@ -1,4 +0,0 @@
win_cert_dir: '{{win_output_dir}}\win_certificate .ÅÑŚÌβŁÈ [$!@^&test(;)]'
key_password: password
subj_thumbprint: 'BD7AF104CF1872BDB518D95C9534EA941665FD27'
root_thumbprint: 'BC05633694E675449136679A658281F17A191087'

@ -1,20 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDKDCCAhCgAwIBAgIJAP1vIdGgMJv/MA0GCSqGSIb3DQEBCwUAMCgxGTAXBgNV
BAMMEHJvb3QuYW5zaWJsZS5jb20xCzAJBgNVBAYTAlVTMCAXDTE3MTIxNTA4Mzkz
MloYDzIwODYwMTAyMDgzOTMyWjAoMRkwFwYDVQQDDBByb290LmFuc2libGUuY29t
MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMmq
YT8eZY6rFQKnmScUGnnUH1tLQ+3WQpfKiWygCUSb1CNqO3J1u3pGMEqYM58LK4Kr
Mpskv7K1tCV/EMZqGTqXAIfSLy9umlb/9C3AhL9thBPn5I9dam/EmrIZktI9/w5Y
wBXn4toe+OopA3QkMQh9BUjUCPb9fdOI+ir7OGFZMmxXmiM64+BEeywM2oSGsdZ9
5hU378UBu2IX4+OAV8Fbr2l6VW+Fxg/tKIOo6Bs46Pa4EZgtemOqs3kxYBOltBTb
vFcLsLa4KYVu5Ge5YfB0Axfaem7PoP8IlMs8gxyojZ/r0o5hzxUcYlL/h8GeeoLW
PFFdiAS+UgxWINOqNXMCAwEAAaNTMFEwHQYDVR0OBBYEFLp9k4LmOnAR4ROrqhb+
CFdbk2+oMB8GA1UdIwQYMBaAFLp9k4LmOnAR4ROrqhb+CFdbk2+oMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGksycHsjGbXfWfuhQh+CvXk/A2v
MoNgiHtNMTGliVNgoVp1B1rj4x9xyZ8YrO8GAmv8jaCwCShd0B5Ul4aZVk1wglVv
lFAwb4IAZN9jv9+fw5BRzQ2tLhkVWIEwx6pZkhGhhjBvMaplLN5JwBtsdZorFbm7
wuKiUKcFAM28acoOhCmOhgyNNBZpZn5wXaQDY43AthJOhitAV7vph4MPUkwIJnOh
MA5GJXEqS58TE9z9pkhQnn9598G8tmOXyA2erAoM9JAXM3EYHxVpoHBb9QRj6WAw
XVBo6qRXkwjNEM5CbnD4hVIBsdkOGsDrgd4Q5izQZ3x+jFNkdL/zPsXjJFw=
-----END CERTIFICATE-----

@ -1,28 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAyaphPx5ljqsVAqeZJxQaedQfW0tD7dZCl8qJbKAJRJvUI2o7
cnW7ekYwSpgznwsrgqsymyS/srW0JX8QxmoZOpcAh9IvL26aVv/0LcCEv22EE+fk
j11qb8SashmS0j3/DljAFefi2h746ikDdCQxCH0FSNQI9v1904j6Kvs4YVkybFea
Izrj4ER7LAzahIax1n3mFTfvxQG7Yhfj44BXwVuvaXpVb4XGD+0og6joGzjo9rgR
mC16Y6qzeTFgE6W0FNu8VwuwtrgphW7kZ7lh8HQDF9p6bs+g/wiUyzyDHKiNn+vS
jmHPFRxiUv+HwZ56gtY8UV2IBL5SDFYg06o1cwIDAQABAoIBAFRpZNsutgPJyLmb
vZeF6q8kAxwLnRtom+c9d9hoBHkbYOiSBuAaN6cuyffvTWw9GLFRR5V5BGSheg5X
6YWj03uayTYQ3H9WJHRWHrcn5mjaRnaukhUQXQT7nmT+H16xZJl0vLJupZ33aOla
0X9DxuJusk+RsU7xPEHXDCABl8/m7v3cFttUBughGBG5oDuzKlFbhXPwA8/yeJ1v
qdXKxENi9HO4X5fH1l0vFNIhEqvUVKjw/AzapYtr+bv1wssoNAzvhT7CFa2GjPQ0
Ibcq3+RxyAN4iQVITy86Yl4LW1jLx63wbg9q1WG/ca9K/OEAuT7ebJNeMYmM+kf7
sf6A8wECgYEA+nnLJ4QtANtAs6nmDC106DTx1cOf3Yq9JOAvmGLomF/8SrUzZbXM
F+JcZcttXuuFIFcZD0K7fFP9sx2ITH//BS5V0B0x7Z2olWexVjR6/5pOVFPu19ow
tyDCNi5BlTPbvSr/fAxjmO9SgVTb8oG66i4mi0Xn5bp1E441KdvNsHECgYEAzhz/
+SjFJlJcGNvMmgfAbfv6McUv7TKrPIvVkA++Gi5QdqJjkuzL1uTfgWIY/9iDByMd
W36rFTkYrw6LTMF2dkMjul72Kkco3UExSzOmF4lFmCt3DZW6a6CExKpwk4kF2RnX
GRD0FoZZown3RbPHi9rsWxjyVy/yKGwnvXYndiMCgYEA6rnIUDfllK/jansFQtQ2
goVbPGAfKJYjurL852mJX4JUBA7bI63CnX9b52lEDXfZQf1dVpfK6zAqx/gdCtPI
QSqy8FzrtSnSGnEaFxcHTRFl5lDhuxaWIIdqeSvP+eqnOhdZZP6XN3LPdrP3isNY
Tq0BIfNY5khd/v19hMSfdYECgYBQ8h6tMY/LrwiwUpIV4/l0uELYDQL3erC5RImI
3EXiblH3ZWsJpqmfKZ+FZos+3z8GLIo5BpQV76h8B5A5grkNVOzRIr42eF/aFOJR
EGWoVKbaTiehVC40WoQJ4I35wxRi4L0TAQ97USQe3akY3LP/fujYFgIGr7PAoEkz
JRX2VQKBgQDir8/a3FZVo6nYI8zIhBz8xqZJIgvlYQqiQFFwADu5eNPMvNIaVy+6
7HKibGM2jPkuS2KHdc8WUp8IrRRMui04qE7kRxVu41QXEBfPiDvrvAQf8SfJe631
XvYeZr7HKY4NI5J0ENcb54d7DLQ8a1/wL/GeLVrfUWG35Ra5MW57Og==
-----END RSA PRIVATE KEY-----

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIC0TCCAbkCCQC/MtOBa1UDpzANBgkqhkiG9w0BAQsFADAoMRkwFwYDVQQDDBBy
b290LmFuc2libGUuY29tMQswCQYDVQQGEwJVUzAgFw0xNzEyMTUwODU2MzBaGA8y
MDg2MDEwMjA4NTYzMFowKzEcMBoGA1UEAwwTc3ViamVjdC5hbnNpYmxlLmNvbTEL
MAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDszqdF
So3GlVP1xUnN4bSPrFRFiOl/Mqup0Zn5UJJUR9wLnRD+OLcq7kKin6hYqozSu7cC
+BnWQoq7vGSSNVqv7BqFMwzGJt9IBUQv0UqIQkA/duUdKdAiMn2PQRsNDnkWEbTj
4xsitItVNv84cDG0lkZBYyTgfyZlZLZWplkpUQkrZhoFCekZRJ+ODrqNW3W560rr
OUIh+HiQeBqocat6OdxgICBqpUh8EVo1iha3DXjGN08q5utg6gmbIl2VBaVJjfyd
wnUSqHylJwh6WCIEh+HXsn4ndfNWSN/fDqvi5I10V1j6Zos7yqQf8qAezUAm6eSq
hLgZz0odq9DsO4HHAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFK5mVIJ2D+kI0kk
sxnW4ibWFjzlYFYPYrZg+2JFIVTbKBg1YzyhuIKm0uztqRxQq5iLn/C/uponHoqF
7KDQI37KAJIQdgSva+mEuO9bZAXg/eegail2hN6np7HjOKlPu23s40dAbFrbcOWP
VbsBEPDP0HLv6OgbQWzNlE9HO1b7pX6ozk3q4ULO7IR85P6OHYsBBThL+qsOTzg/
gVknuB9+n9hgNqZcAcXBLDetOM9aEmYJCGk0enYP5UGLYpseE+rTXFbRuHTPr1o6
e8BetiSWS/wcrV4ZF5qr9NiYt5eD6JzTB5Rn5awxxj0FwMtrBu003lLQUWxsuTzz
35/RLY4=
-----END CERTIFICATE-----

@ -1,28 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA7M6nRUqNxpVT9cVJzeG0j6xURYjpfzKrqdGZ+VCSVEfcC50Q
/ji3Ku5Cop+oWKqM0ru3AvgZ1kKKu7xkkjVar+wahTMMxibfSAVEL9FKiEJAP3bl
HSnQIjJ9j0EbDQ55FhG04+MbIrSLVTb/OHAxtJZGQWMk4H8mZWS2VqZZKVEJK2Ya
BQnpGUSfjg66jVt1uetK6zlCIfh4kHgaqHGrejncYCAgaqVIfBFaNYoWtw14xjdP
KubrYOoJmyJdlQWlSY38ncJ1Eqh8pScIelgiBIfh17J+J3XzVkjf3w6r4uSNdFdY
+maLO8qkH/KgHs1AJunkqoS4Gc9KHavQ7DuBxwIDAQABAoIBAQDfjqBfS+jYZrUi
uqPYV5IMaNYN5xj4Wi+xXA0OT0A1jLlxxU/7kDNrtg72U9+sBSZ483nstag+nAc5
ALu5Q+FfX3gR84XFs4DrDv22XtEMHe9leqsFgynYfu4GRaJyCw3JBeJNmWNOuj8n
rYn4EAL8xzmAFUcFIURwSEnTN6vI0cS09nQukz+9CIBuGr7TPMET8YlATDJcH+Ua
EGZ9MAFXdKF6adC2nrCVBDNr8mUEpK1XdQcPH2bvcTuZ3Jj5AF2rOrcHq4FZUm97
8PaMH6Sarxhwl+ycwrKbU5aEzUYTk67k0V6m9lyvH9z3O3Y84Tr3cZZ5WxdnG6Ri
72MFlfgRAoGBAP8wA+KWJ/ttmEXAoSX4J2fPl7X1RhR+1zPNdLY7iX0uNstL8IFH
vUN9JHi1Tr7llav+2bUTOu2EMDVmDWZH0s/qKOn+GmqIQLp0441fVAiamTcgwGKE
Wwsu4dg10IJ9akHIIbrILT0CvRcIRf67EYLBj3ZwfR+wF1ncefbsxWA9AoGBAO2P
qGMn+yrIi5DZF23x6iD2Y7bIdlUmqIqwb99XhW+3YJmRuh1EuN6XP2bIveRa9xvm
Q7bbcQM0Yv2c7eTyxpzz2I4bmnccVbs6M1VhtkyQEy5+X5yOl9wnitaaUrbWFy/w
kDPuISjLl3xDlxd6dbjf70fkG5oogx5c5toEyWZTAoGAK1CHGErMdozfr9dGgx9f
8Or3oVcEki4FcTGKgfQRHkJd4pv9MrRul6oCKsr7lsN5aDxVz7p34iDx3d54n8fJ
LKleUHllGngOJJf6l+B6bwtuvkC85vv4SCmpA/3+amfHRWsm7oFTzGtOlT4+Q0KV
clBQfZYSZvKIxCP8P8ForzECgYEAjDOad1qjOy68X7Ifx71cJjQDyV4pqDt2gNN8
Ut1+XN5m3ntI0fk6+fNdcbXLjDe7WvXcxNBhtDh4q6CwLcyyNvMavVPBJ8bLOgIx
RZSzWCA3kdr3ZpgpO78Ci4DsjAdyC9L36A4D9+Wf87CYPT0CuSdAOrd/Ks36BDNj
8wucKQ0CgYAaRwQ18nkemrpQ/+EQgEWnWfqgB+6T4ygZ4ZTym0FAtG7CdLxvCi8V
toyn+zi+yFTRFXHDmvg9HLIIMK/hRQjgc8Ns5nDwgQlGwCZTvjVbD4anCr1IWuky
owvxKWsHseNilKrnAk2maQxrrrpSk8QWrp2CFw04LsWGTxtFvstBmg==
-----END RSA PRIVATE KEY-----

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save