forked from ansible/ansible
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Windows: A module for creating Toast notifications on Modern Windows …
…versions. (ansible#26675) * replace duff commit version of win_toast * change expire_mins to expire_secs and add example showing use of async * fix metadata version to keep sanity --test validate-modules happy * code review fixes and change expire_secs to expire_seconds * add first pass integration tests for win_toast * win_toast no longer fails if there are no logged in users to notify (it sets a toast_sent false if this happens) * yaml lint clean up of setup.yml in win_toast integration tests * improve exception and stack trace if the notifier cannot be created, following feedback from dag * removed unwanted 'echo' input parameters from return vals; added to CHANGELOG.md, removed _seconds units from module params; updated tests to match
- Loading branch information
1 parent
4ba7d05
commit 8f9b885
Showing
7 changed files
with
332 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#!powershell | ||
# This file is part of Ansible | ||
|
||
# Copyright (c) 2017, Jon Hawkesworth (@jhawkesworth) <[email protected]> | ||
# 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.psm1 | ||
|
||
$ErrorActionPreference = "Stop" | ||
|
||
# version check | ||
$osversion = [Environment]::OSVersion | ||
$lowest_version = 10 | ||
if ($osversion.Version.Major -lt $lowest_version ) { | ||
Fail-Json $result "Sorry, this version of windows, $osversion, does not support Toast notifications. Toast notifications are available from version $lowest_version" | ||
} | ||
|
||
$stopwatch = [system.diagnostics.stopwatch]::startNew() | ||
$now = [DateTime]::Now | ||
$default_title = "Notification: " + $now.ToShortTimeString() | ||
|
||
$params = Parse-Args $args -supports_check_mode $true | ||
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false | ||
|
||
$expire_seconds = Get-AnsibleParam -obj $params -name "expire" -type "int" -default 45 | ||
$group = Get-AnsibleParam -obj $params -name "group" -type "str" -default "Powershell" | ||
$msg = Get-AnsibleParam -obj $params -name "msg" -type "str" -default "Hello world!" | ||
$popup = Get-AnsibleParam -obj $params -name "popup" -type "bool" -default $true | ||
$tag = Get-AnsibleParam -obj $params -name "tag" -type "str" -default "Ansible" | ||
$title = Get-AnsibleParam -obj $params -name "title" -type "str" -default $default_title | ||
|
||
$timespan = New-TimeSpan -Seconds $expire_seconds | ||
$expire_at = $now + $timespan | ||
$expire_at_utc = $($expire_at.ToUniversalTime()|Out-String).Trim() | ||
|
||
$result = @{ | ||
changed = $false | ||
expire_at = $expire_at.ToString() | ||
expire_at_utc = $expire_at_utc | ||
toast_sent = $false | ||
} | ||
|
||
# If no logged in users, there is no notifications service, | ||
# and no-one to read the message, so exit but do not fail | ||
# if there are no logged in users to notify. | ||
|
||
if ((get-process -name explorer -EA silentlyContinue).Count -gt 0){ | ||
|
||
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null | ||
$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText01) | ||
|
||
#Convert to .NET type for XML manipulation | ||
$toastXml = [xml] $template.GetXml() | ||
$toastXml.GetElementsByTagName("text").AppendChild($toastXml.CreateTextNode($title)) > $null | ||
# TODO add subtitle | ||
|
||
#Convert back to WinRT type | ||
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument | ||
$xml.LoadXml($toastXml.OuterXml) | ||
|
||
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml) | ||
$toast.Tag = $tag | ||
$toast.Group = $group | ||
$toast.ExpirationTime = $expire_at | ||
$toast.SuppressPopup = -not $popup | ||
|
||
try { | ||
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($msg) | ||
if (-not $check_mode) { | ||
$notifier.Show($toast) | ||
$result.toast_sent = $true | ||
Start-Sleep -Seconds $expire_seconds | ||
} | ||
} catch { | ||
$excep = $_ | ||
$result.exception = $excep.ScriptStackTrace | ||
Fail-Json -obj $result -message "Failed to create toast notifier: $($excep.Exception.Message)" | ||
} | ||
} else { | ||
$result.toast_sent = $false | ||
$result.no_toast_sent_reason = 'No logged in users to notifiy' | ||
} | ||
|
||
$endsend_at = Get-Date| Out-String | ||
$stopwatch.Stop() | ||
|
||
$result.time_taken = $stopwatch.Elapsed.TotalSeconds | ||
$result.sent_localtime = $endsend_at.Trim() | ||
|
||
Exit-Json $result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
#!/usr/bin/python | ||
# -*- coding: utf-8 -*- | ||
|
||
# (c) 2017, Jon Hawkesworth (@jhawkesworth) <[email protected]> | ||
# 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_toast | ||
version_added: "2.4" | ||
short_description: Sends Toast windows notification to logged in users on Windows 10 or later hosts | ||
description: | ||
- Sends alerts which appear in the Action Center area of the windows desktop. | ||
options: | ||
expire: | ||
description: | ||
- How long in seconds before the notification expires. | ||
default: 45 | ||
group: | ||
description: | ||
- Which notification group to add the notification to. | ||
default: Powershell | ||
msg: | ||
description: | ||
- The message to appear inside the notification. May include \n to format the message to appear within the Action Center. | ||
default: 'Hello, World!' | ||
popup: | ||
description: | ||
- If false, the notification will not pop up and will only appear in the Action Center. | ||
type: bool | ||
default: yes | ||
tag: | ||
description: | ||
- The tag to add to the notification. | ||
default: Ansible | ||
title: | ||
description: | ||
- The notification title, which appears in the pop up.. | ||
default: Notification HH:mm | ||
author: | ||
- Jon Hawkesworth (@jhawkesworth) | ||
notes: | ||
- This module must run on a windows 10 or Server 2016 host, so ensure your play targets windows hosts, or delegates to a windows host. | ||
- The module does not fail if there are no logged in users to notify. | ||
- Messages are only sent to the local host where the module is run. | ||
- You must run this module with async, otherwise it will hang until the expire period has passed. | ||
''' | ||
|
||
EXAMPLES = r''' | ||
- name: Warn logged in users of impending upgrade (note use of async to stop the module from waiting until notification expires). | ||
win_toast: | ||
expire: 60 | ||
title: System Upgrade Notification | ||
msg: Automated upgrade about to start. Please save your work and log off before {{ deployment_start_time }} | ||
async: 60 | ||
poll: 0 | ||
''' | ||
|
||
RETURN = r''' | ||
expire_at_utc: | ||
description: Calculated utc date time when the notification expires. | ||
returned: allways | ||
type: string | ||
sample: 07 July 2017 04:50:54 | ||
no_toast_sent_reason: | ||
description: Text containing the reason why a notification was not sent. | ||
returned: when no logged in users are detected | ||
type: string | ||
sample: No logged in users to notify | ||
sent_localtime: | ||
description: local date time when the notification was sent. | ||
returned: allways | ||
type: string | ||
sample: 07 July 2017 05:45:54 | ||
time_taken: | ||
description: How long the module took to run on the remote windows host in seconds. | ||
returned: allways | ||
type: float | ||
sample: 0.3706631999999997 | ||
toast_sent: | ||
description: Whether the module was able to send a toast notification or not. | ||
returned: allways | ||
type: boolean | ||
sample: false | ||
''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
windows/ci/group2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
- name: Set up tests | ||
include_tasks: setup.yml | ||
|
||
- name: Test in normal mode | ||
include_tasks: tests.yml | ||
vars: | ||
in_check_mode: no | ||
|
||
- name: Test in check mode | ||
include_tasks: tests.yml | ||
vars: | ||
in_check_mode: yes | ||
check_mode: yes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
- name: Get OS version | ||
win_shell: '[Environment]::OSVersion.Version.Major' | ||
register: os_version | ||
|
||
- name: Get logged in user count (using explorer exe as a proxy) | ||
win_shell: (get-process -name explorer -EA silentlyContinue).Count | ||
register: user_count | ||
|
||
- name: debug os_version | ||
debug: | ||
var: os_version | ||
verbosity: 2 | ||
|
||
- name: debug user_count | ||
debug: | ||
var: user_count | ||
verbosity: 2 | ||
|
||
- name: Set fact if toast cannot be made | ||
set_fact: | ||
can_toast: False | ||
when: os_version.stdout|int < 10 | ||
|
||
- name: Set fact if toast can be made | ||
set_fact: | ||
can_toast: True | ||
when: os_version.stdout|int >= 10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
- name: Warn user | ||
win_toast: | ||
expire_seconds: 10 | ||
msg: Keep calm and carry on. | ||
register: msg_result | ||
ignore_errors: True | ||
|
||
- name: Test msg_result when can_toast is true (normal mode, users) | ||
assert: | ||
that: | ||
- not msg_result|failed | ||
- msg_result.time_taken > 10 | ||
when: | ||
- can_toast == True | ||
- in_check_mode == False | ||
- user_count.stdout|int > 0 | ||
|
||
- name: Test msg_result when can_toast is true (normal mode, no users) | ||
assert: | ||
that: | ||
- not msg_result|failed | ||
- msg_result.time_taken > 0.1 | ||
- msg_result.toast_sent == False | ||
when: | ||
- can_toast == True | ||
- in_check_mode == False | ||
- user_count.stdout|int == 0 | ||
|
||
- name: Test msg_result when can_toast is true (check mode, users) | ||
assert: | ||
that: | ||
- not msg_result|failed | ||
- msg_result.time_taken > 0.1 | ||
when: | ||
- can_toast == True | ||
- in_check_mode == True | ||
|
||
- name: Test msg_result when can_toast is true (check mode, no users) | ||
assert: | ||
that: | ||
- not msg_result|failed | ||
- msg_result.time_taken > 0.1 | ||
- msg_result.toast_sent == False | ||
when: | ||
- can_toast == True | ||
- in_check_mode == True | ||
- user_count.stdout|int == 0 | ||
|
||
- name: Test msg_result when can_toast is false | ||
assert: | ||
that: | ||
- msg_result|failed | ||
when: can_toast == False | ||
|
||
- name: Warn user again | ||
win_toast: | ||
expire_seconds: 10 | ||
msg: Keep calm and carry on. | ||
register: msg_result2 | ||
ignore_errors: True | ||
|
||
- name: Test msg_result2 when can_toast is true (normal mode, users) | ||
assert: | ||
that: | ||
- not msg_result2|failed | ||
- msg_result2.time_taken > 10 | ||
when: | ||
- can_toast == True | ||
- in_check_mode == False | ||
- user_count.stdout|int > 0 | ||
|
||
- name: Test msg_result2 when can_toast is true (normal mode, no users) | ||
assert: | ||
that: | ||
- not msg_result2|failed | ||
- msg_result2.time_taken > 0.1 | ||
when: | ||
- can_toast == True | ||
- in_check_mode == False | ||
- user_count.stdout|int == 0 | ||
|
||
- name: Test msg_result2 when can_toast is true (check mode, users) | ||
assert: | ||
that: | ||
- not msg_result2|failed | ||
- msg_result2.time_taken > 0.1 | ||
when: | ||
- can_toast == True | ||
- in_check_mode == False | ||
- user_count.stdout|int > 0 | ||
|
||
- name: Test msg_result2 when can_toast is true (check mode, no users) | ||
assert: | ||
that: | ||
- not msg_result2|failed | ||
- msg_result2.time_taken > 0.1 | ||
when: | ||
- can_toast == True | ||
- in_check_mode == False | ||
- user_count.stdout|int == 0 | ||
|
||
- name: Test msg_result2 when can_toast is false | ||
assert: | ||
that: | ||
- msg_result2|failed | ||
when: can_toast == False |