Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add win_wait_for_process #40404

Merged
merged 9 commits into from
Aug 28, 2018
162 changes: 162 additions & 0 deletions lib/ansible/modules/windows/win_wait_for_process.ps1
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is support_check_mode true intended here?
I suggest looking at other wait_for module code to see how it behaves in check mode.

Copy link
Contributor Author

@crossan007 crossan007 Jun 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this. This module doesn't actually change anything, so check_mode is almost irrelevant; however, I suspect module users may still wish for a process change or presence check to occur if they're running a --check

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, since this module does not make a change, it should simply work in check-mode identically to normal mode. So I agree with @crossan007


$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
99 changes: 99 additions & 0 deletions lib/ansible/modules/windows/win_wait_for_process.py
Original file line number Diff line number Diff line change
@@ -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
'''