diff --git a/lib/ansible/modules/windows/win_wait_for_process.ps1 b/lib/ansible/modules/windows/win_wait_for_process.ps1 new file mode 100644 index 00000000000..4155b7926fd --- /dev/null +++ b/lib/ansible/modules/windows/win_wait_for_process.ps1 @@ -0,0 +1,162 @@ +#!powershell +# 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) + +#Requires -Module Ansible.ModuleUtils.Legacy +#Requires -Module Ansible.ModuleUtils.FileUtil + +$ErrorActionPreference = "Stop" + +$params = Parse-Args -arguments $args -supports_check_mode $true + +$process_name_exact = Get-AnsibleParam -obj $params -name "process_name_exact" -type "list" +$process_name_pattern = Get-AnsibleParam -obj $params -name "process_name_pattern" -type "str" +$process_id = Get-AnsibleParam -obj $params -name "pid" -type "int" -default 0 #pid is a reserved variable in PowerShell. use process_id instead. +$owner = Get-AnsibleParam -obj $params -name "owner" -type "str" +$sleep = Get-AnsibleParam -obj $params -name "sleep" -type "int" -default 1 +$pre_wait_delay = Get-AnsibleParam -obj $params -name "pre_wait_delay" -type "int" -default 0 +$post_wait_delay = Get-AnsibleParam -obj $params -name "post_wait_delay" -type "int" -default 0 +$process_min_count = Get-AnsibleParam -obj $params -name "process_min_count" -type "int" -default 1 +$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent" +$timeout = Get-AnsibleParam -obj $params -name "timeout" -type "int" -default 300 + +$result = @{ + changed = $false +} + +# validate the input +if ($state -eq "absent" -and $sleep -ne 1) +{ + Add-Warning $result "sleep parameter has no effect when waiting for a process to stop." +} + +if ($state -eq "absent" -and $process_min_count -ne 1) +{ + Add-Warning $result "process_min_count parameter has no effect when waiting for a process to stop." +} + +if (($process_name_exact -or $process_name_pattern) -and $process_id) +{ + Fail-json $result "process_id may not be used with process_name_exact or process_name_pattern." +} +if ($process_name_exact -and $process_name_pattern) +{ + Fail-json $result "process_name_exact and process_name_pattern may not be used at the same time." +} + +if (-not ($process_name_exact -or $process_name_pattern -or $process_id -or $owner)) +{ + Fail-json $result "at least one of: process_name_exact, process_name_pattern, process_id, or owner must be supplied." +} + +$module_start = Get-Date + +#Get-Process doesn't actually return a UserName value, so get it from WMI. +Function Get-ProcessMatchesFilter { + [cmdletbinding()] + Param( + [String] + $Owner, + $ProcessNameExact, + $ProcessNamePattern, + [int] + $ProcessId + ) + + $CIMProcesses = Get-CimInstance Win32_Process + foreach ($CIMProcess in $CIMProcesses) + { + $include = $true + if(-not [String]::IsNullOrEmpty($ProcessNamePattern)) + { + #if a process name was specified in the filter, validate that here. + $include = $include -and ($CIMProcess.ProcessName -match $ProcessNamePattern) + } + if($ProcessNameExact -is [Array] -or (-not [String]::IsNullOrEmpty($ProcessNameExact))) + { + #if a process name was specified in the filter, validate that here. + if ($ProcessNameExact -is [Array] ) + { + $include = $include -and ($ProcessNameExact -contains $CIMProcess.ProcessName) + } + else { + $include = $include -and ($ProcessNameExact -eq $CIMProcess.ProcessName) + } + } + if ($ProcessId -and $ProcessId -ne 0) + { + # if a PID was specified in the filger, validate that here. + $include = $include -and ($CIMProcess.ProcessId -eq $ProcessId) + } + if (-not [String]::IsNullOrEmpty($Owner) ) + { + # if an owner was specified in the filter, validate that here. + $include = $include -and ($($(Invoke-CimMethod -InputObject $CIMProcess -MethodName GetOwner).User) -eq $Owner) + } + + if ($include) + { + $CIMProcess | Select-Object -Property ProcessId, ProcessName, @{name="Owner";Expression={$($(Invoke-CimMethod -InputObject $CIMProcess -MethodName GetOwner).User)}} + } + } +} + +Start-Sleep -Seconds $pre_wait_delay +if ($state -eq "present" ) { + #wait for a process to start + $Processes = @() + $attempts = 0 + Do { + if (((Get-Date) - $module_start).TotalSeconds -gt $timeout) + { + $result.elapsed = ((Get-Date) - $module_start).TotalSeconds + Fail-Json $result "timeout while waiting for $process_name to start. waited $timeout seconds" + } + + $Processes = Get-ProcessMatchesFilter -Owner $owner -ProcessNameExact $process_name_exact -ProcessNamePattern $process_name_pattern -ProcessId $process_id + Start-Sleep -Seconds $sleep + $attempts ++ + $ProcessCount = $null + if ($Processes -is [array]) { + $ProcessCount = $Processes.count + } + elseif ($null -ne $Processes) { + $ProcessCount = 1 + } + else { + $ProcessCount = 0 + } + } While ($ProcessCount -lt $process_min_count) + + if ($attempts -gt 0) + { + $result.changed = $true + } + $result.matched_processess = $Processes +} +elseif ($state -eq "absent") { + #wait for a process to stop + $Processes = Get-ProcessMatchesFilter -Owner $owner -ProcessNameExact $process_name_exact -ProcessNamePattern $process_name_pattern -ProcessId $process_id + $result.matched_processes = $Processes + $ProcessCount = $(if ($Processes -is [array]) { $Processes.count } elseif ($Processes){ 1 } else {0}) + if ($ProcessCount -gt 0 ) + { + try { + Wait-Process -Id $($Processes | Select-Object -ExpandProperty ProcessId) -Timeout $timeout -ErrorAction Stop + $result.changed = $true + } + catch { + $result.elapsed = ((Get-Date) - $module_start).TotalSeconds + Fail-Json $result "$($_.Exception.Message). timeout while waiting for $process_name to stop. waited $timeout seconds" + } + } + else{ + $result.changed = $false + + } +} +Start-Sleep -Seconds $post_wait_delay +$result.elapsed = ((Get-Date) - $module_start).TotalSeconds +Exit-Json $result \ No newline at end of file diff --git a/lib/ansible/modules/windows/win_wait_for_process.py b/lib/ansible/modules/windows/win_wait_for_process.py new file mode 100644 index 00000000000..cf16affa625 --- /dev/null +++ b/lib/ansible/modules/windows/win_wait_for_process.py @@ -0,0 +1,99 @@ +#!/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_process +version_added: '2.7' +short_description: Waits for a process to exist or not exist before continuing. +description: +- Waiting for a process to start or stop is useful when Windows services + behave poorly and do not enumerate external dependencies in their + manifest. +options: + process_name_exact: + description: + - The name of the process(es) for which to wait. + - Must inclue the file extension of the process binary (.exe) + process_name_pattern: + description: + - RegEx pattern matching desired process(es) + sleep: + description: + - Number of seconds to sleep between checks. + - Only applies when waiting for a process to start. Waiting for a process to start + does not have a native non-polling mechanism. Waiting for a stop uses native PowerShell + and does not require polling. + default: 1 + process_min_count: + description: + - Minimum number of process matching the supplied pattern to satisfy C(present) condition. + - Only applies to C(present). + default: 1 + state: + description: + - When checking for a running process C(present) will block execution + until the process exists, or until the timeout has been reached. + C(absent) will block execution untile the processs no longer exists, + or until the timeout has been reached. + - When waiting for C(present), the module will return changed only if + the process was not present on the initial check but became present on + subsequent checks. + - If, while waiting for C(absent), new processes matching the supplied + pattern are started, these new processes will not be included in the + action. + default: present + choices: [ absent, present ] + timeout: + description: + - The maximum number of seconds to wait for a for a process to start or stop + before erroring out. + default: 300 +author: +- Charles Crossan (@crossan007) +''' + +EXAMPLES = r''' +- name: Wait 300 seconds for all Oracle VirtualBox processes to stop. (VBoxHeadless, VirtualBox, VBoxSVC) + win_wait_for_process: + process_name: "v(irtual)?box(headless|svc)?" + state: absent + timeout: 500 + + +- name: Wait 300 seconds for 3 instances of cmd to start, waiting 5 seconds between each check + win_wait_for_process: + process_name: "cmd\\.exe" + state: present + timeout: 500 + sleep: 5 + process_min_count: 3 + +''' + +RETURN = r''' +elapsed: + description: The elapsed seconds between the start of poll and the end of the + module. + returned: always + type: float + sample: 3.14159265 +changed: + description: True if a process was started or stopped during the module execution. + returned: always + type: bool +matched_processes: + description: Count of processes stopped or started. + returned: always + type: int +'''