Skip to content

Commit

Permalink
add new module vmware_guest_sendkey
Browse files Browse the repository at this point in the history
  • Loading branch information
Tomorrow9 committed May 23, 2019
1 parent 8c29c78 commit fc5c6d4
Show file tree
Hide file tree
Showing 3 changed files with 402 additions and 0 deletions.
354 changes: 354 additions & 0 deletions lib/ansible/modules/cloud/vmware/vmware_guest_sendkey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Ansible Project
# Copyright: (c) 2018, Diane Wang <dianew@vmware.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

ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}

DOCUMENTATION = '''
---
module: vmware_guest_sendkey
short_description: Send USB HID codes to the Virtual Machine's keyboard.
description:
- This module is used to send keystrokes to given virtual machine.
- All parameters and VMware object names are case sensitive.
version_added: '2.9'
author:
- Diane Wang (@Tomorrow9) <dianew@vmware.com>
notes:
- Tested on vSphere 6.5 and 6.7
requirements:
- "python >= 2.6"
- PyVmomi
options:
name:
description:
- Name of the virtual machine.
- This is a required parameter, if parameter C(uuid) is not supplied.
uuid:
description:
- UUID of the instance to gather facts if known, this is VMware's unique identifier.
- This is a required parameter, if parameter C(name) is not supplied.
folder:
description:
- Destination folder, absolute or relative path to find an existing guest.
- This is a required parameter, only if multiple VMs are found with same name.
- The folder should include the datacenter. ESXi server's datacenter is ha-datacenter.
- 'Examples:'
- ' folder: /ha-datacenter/vm'
- ' folder: ha-datacenter/vm'
- ' folder: /datacenter1/vm'
- ' folder: datacenter1/vm'
- ' folder: /datacenter1/vm/folder1'
- ' folder: datacenter1/vm/folder1'
- ' folder: /folder1/datacenter1/vm'
- ' folder: folder1/datacenter1/vm'
- ' folder: /folder1/datacenter1/vm/folder2'
cluster:
description:
- The name of cluster where the virtual machine is running.
- This is a required parameter, if C(esxi_hostname) is not set.
- C(esxi_hostname) and C(cluster) are mutually exclusive parameters.
esxi_hostname:
description:
- The ESXi hostname where the virtual machine is running.
- This is a required parameter, if C(cluster) is not set.
- C(esxi_hostname) and C(cluster) are mutually exclusive parameters.
datacenter:
description:
- The datacenter name to which virtual machine belongs to.
string_send:
description:
- The string will be sent to the virtual machine.
- This string can contain valid special character, alphabet and digit on the keyboard.
keys_send:
description:
- The list of the keys will be sent to the virtual machine.
- 'Valid values are C(ENTER), C(ESC), C(BACKSPACE), C(TAB), C(SPACE), C(CAPSLOCK), C(DELETE), C(CTRL_ALT_DEL),
C(CTRL_C) and C(F1) to C(F12), C(RIGHTARROW), C(LEFTARROW), C(DOWNARROW), C(UPARROW).'
- If both C(keys_send) and C(string_send) are specified, keys in C(keys_send) list will be sent in front of the C(string_send).
extends_documentation_fragment: vmware.documentation
'''

EXAMPLES = '''
- name: send list of keys to virtual machine
vmware_guest_sendkey:
validate_certs: no
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
datacenter: "{{ datacenter_name }}"
folder: "{{ folder_name }}"
name: "{{ vm_name }}"
keys_send:
- TAB
- TAB
- ENTER
delegate_to: localhost
register: keys_num_sent
- name: send a string to virtual machine
vmware_guest_sendkey:
validate_certs: no
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
datacenter: "{{ datacenter_name }}"
folder: "{{ folder_name }}"
name: "{{ vm_name }}"
string_send: "user_logon"
delegate_to: localhost
register: keys_num_sent
'''

RETURN = """
sendkey_facts:
description: display the keys and the number of keys sent to the virtual machine
returned: always
type: dict
sample: {
"virtual_machine": "test_vm",
"keys_send": [
"SPACE",
"DOWNARROW",
"DOWNARROW",
"ENTER"
],
"string_send": null,
"keys_send_number": 4,
"returned_keys_send_number": 4,
}
"""

try:
from pyVmomi import vim, vmodl
except ImportError:
pass

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec


class PyVmomiHelper(PyVmomi):
def __init__(self, module):
super(PyVmomiHelper, self).__init__(module)
self.change_detected = False
self.usb_scan_code_spec = vim.UsbScanCodeSpec()
self.num_keys_send = 0
# HID usage tables https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
# define valid characters and keys value, hex_code, key value and key modifier
self.keys_hid_code = [
(('a', 'A'), '0x04', [('a', []), ('A', ['LEFTSHIFT'])]),
(('b', 'B'), '0x05', [('b', []), ('B', ['LEFTSHIFT'])]),
(('c', 'C'), '0x06', [('c', []), ('C', ['LEFTSHIFT'])]),
(('d', 'D'), '0x07', [('d', []), ('D', ['LEFTSHIFT'])]),
(('e', 'E'), '0x08', [('e', []), ('E', ['LEFTSHIFT'])]),
(('f', 'F'), '0x09', [('f', []), ('F', ['LEFTSHIFT'])]),
(('g', 'G'), '0x0a', [('g', []), ('G', ['LEFTSHIFT'])]),
(('h', 'H'), '0x0b', [('h', []), ('H', ['LEFTSHIFT'])]),
(('i', 'I'), '0x0c', [('i', []), ('I', ['LEFTSHIFT'])]),
(('j', 'J'), '0x0d', [('j', []), ('J', ['LEFTSHIFT'])]),
(('k', 'K'), '0x0e', [('k', []), ('K', ['LEFTSHIFT'])]),
(('l', 'L'), '0x0f', [('l', []), ('L', ['LEFTSHIFT'])]),
(('m', 'M'), '0x10', [('m', []), ('M', ['LEFTSHIFT'])]),
(('n', 'N'), '0x11', [('n', []), ('N', ['LEFTSHIFT'])]),
(('o', 'O'), '0x12', [('o', []), ('O', ['LEFTSHIFT'])]),
(('p', 'P'), '0x13', [('p', []), ('P', ['LEFTSHIFT'])]),
(('q', 'Q'), '0x14', [('q', []), ('Q', ['LEFTSHIFT'])]),
(('r', 'R'), '0x15', [('r', []), ('R', ['LEFTSHIFT'])]),
(('s', 'S'), '0x16', [('s', []), ('S', ['LEFTSHIFT'])]),
(('t', 'T'), '0x17', [('t', []), ('T', ['LEFTSHIFT'])]),
(('u', 'U'), '0x18', [('u', []), ('U', ['LEFTSHIFT'])]),
(('v', 'V'), '0x19', [('v', []), ('V', ['LEFTSHIFT'])]),
(('w', 'W'), '0x1a', [('w', []), ('W', ['LEFTSHIFT'])]),
(('x', 'X'), '0x1b', [('x', []), ('X', ['LEFTSHIFT'])]),
(('y', 'Y'), '0x1c', [('y', []), ('Y', ['LEFTSHIFT'])]),
(('z', 'Z'), '0x1d', [('z', []), ('Z', ['LEFTSHIFT'])]),
(('1', '!'), '0x1e', [('1', []), ('!', ['LEFTSHIFT'])]),
(('2', '@'), '0x1f', [('2', []), ('@', ['LEFTSHIFT'])]),
(('3', '#'), '0x20', [('3', []), ('#', ['LEFTSHIFT'])]),
(('4', '$'), '0x21', [('4', []), ('$', ['LEFTSHIFT'])]),
(('5', '%'), '0x22', [('5', []), ('%', ['LEFTSHIFT'])]),
(('6', '^'), '0x23', [('6', []), ('^', ['LEFTSHIFT'])]),
(('7', '&'), '0x24', [('7', []), ('&', ['LEFTSHIFT'])]),
(('8', '*'), '0x25', [('8', []), ('*', ['LEFTSHIFT'])]),
(('9', '('), '0x26', [('9', []), ('(', ['LEFTSHIFT'])]),
(('0', ')'), '0x27', [('0', []), (')', ['LEFTSHIFT'])]),
(('-', '_'), '0x2d', [('-', []), ('_', ['LEFTSHIFT'])]),
(('=', '+'), '0x2e', [('=', []), ('+', ['LEFTSHIFT'])]),
(('[', '{'), '0x2f', [('[', []), ('{', ['LEFTSHIFT'])]),
((']', '}'), '0x30', [(']', []), ('}', ['LEFTSHIFT'])]),
(('\\', '|'), '0x31', [('\\', []), ('|', ['LEFTSHIFT'])]),
((';', ':'), '0x33', [(';', []), (':', ['LEFTSHIFT'])]),
(('\'', '"'), '0x34', [('\'', []), ('"', ['LEFTSHIFT'])]),
(('`', '~'), '0x35', [('`', []), ('~', ['LEFTSHIFT'])]),
((',', '<'), '0x36', [(',', []), ('<', ['LEFTSHIFT'])]),
(('.', '>'), '0x37', [('.', []), ('>', ['LEFTSHIFT'])]),
(('/', '?'), '0x38', [('/', []), ('?', ['LEFTSHIFT'])]),
('ENTER', '0x28', [('', [])]),
('ESC', '0x29', [('', [])]),
('BACKSPACE', '0x2a', [('', [])]),
('TAB', '0x2b', [('', [])]),
('SPACE', '0x2c', [(' ', [])]),
('CAPSLOCK', '0x39', [('', [])]),
('F1', '0x3a', [('', [])]),
('F2', '0x3b', [('', [])]),
('F3', '0x3c', [('', [])]),
('F4', '0x3d', [('', [])]),
('F5', '0x3e', [('', [])]),
('F6', '0x3f', [('', [])]),
('F7', '0x40', [('', [])]),
('F8', '0x41', [('', [])]),
('F9', '0x42', [('', [])]),
('F10', '0x43', [('', [])]),
('F11', '0x44', [('', [])]),
('F12', '0x45', [('', [])]),
('DELETE', '0x4c', [('', [])]),
('CTRL_ALT_DEL', '0x4c', [('', ['CTRL', 'ALT'])]),
('CTRL_C', '0x06', [('', ['CTRL'])]),
('RIGHTARROW', '0x4f', [('', [])]),
('LEFTARROW', '0x50', [('', [])]),
('DOWNARROW', '0x51', [('', [])]),
('UPARROW', '0x52', [('', [])]),
]

@staticmethod
def hid_to_hex(hid_code):
return int(hid_code, 16) << 16 | 0o0007

def get_hid_from_key(self, key):
if key == ' ':
return '0x2c', []
for keys_name, key_code, keys_value in self.keys_hid_code:
if isinstance(keys_name, tuple):
for keys in keys_value:
if key == keys[0]:
return key_code, keys[1]
else:
if key == keys_name:
return key_code, keys_value[0][1]

def get_key_event(self, hid_code, modifiers):
key_event = vim.UsbScanCodeSpecKeyEvent()
key_modifier = vim.UsbScanCodeSpecModifierType()
key_modifier.leftAlt = False
key_modifier.leftControl = False
key_modifier.leftGui = False
key_modifier.leftShift = False
key_modifier.rightAlt = False
key_modifier.rightControl = False
key_modifier.rightGui = False
key_modifier.rightShift = False
# rightShift, rightControl, rightAlt, leftGui, rightGui are not used
if "LEFTSHIFT" in modifiers:
key_modifier.leftShift = True
if "CTRL" in modifiers:
key_modifier.leftControl = True
if "ALT" in modifiers:
key_modifier.leftAlt = True
key_event.modifiers = key_modifier
key_event.usbHidCode = self.hid_to_hex(hid_code)

return key_event

def get_sendkey_facts(self, vm_obj, returned_value=0):
sendkey_facts = dict()
if vm_obj is not None:
sendkey_facts = dict(
virtual_machine=vm_obj.name,
keys_send=self.params['keys_send'],
string_send=self.params['string_send'],
keys_send_number=self.num_keys_send,
returned_keys_send_number=returned_value,
)

return sendkey_facts

def send_key_to_vm(self, vm_obj):
key_event = None
num_keys_returned = 0
if self.params['keys_send']:
for specified_key in self.params['keys_send']:
key_found = False
for keys in self.keys_hid_code:
if (isinstance(keys[0], tuple) and specified_key in keys[0]) or \
(not isinstance(keys[0], tuple) and specified_key == keys[0]):
hid_code, modifiers = self.get_hid_from_key(specified_key)
key_event = self.get_key_event(hid_code, modifiers)
self.usb_scan_code_spec.keyEvents.append(key_event)
self.num_keys_send += 1
key_found = True
break
if not key_found:
self.module.fail_json(msg="keys_send parameter: '%s' in %s not supported."
% (specified_key, self.params['keys_send']))

if self.params['string_send']:
for char in self.params['string_send']:
key_found = False
for keys in self.keys_hid_code:
if (isinstance(keys[0], tuple) and char in keys[0]) or char == ' ':
hid_code, modifiers = self.get_hid_from_key(char)
key_event = self.get_key_event(hid_code, modifiers)
self.usb_scan_code_spec.keyEvents.append(key_event)
self.num_keys_send += 1
key_found = True
break
if not key_found:
self.module.fail_json(msg="string_send parameter: '%s' contains char: '%s' not supported."
% (self.params['string_send'], char))

if self.usb_scan_code_spec.keyEvents:
try:
num_keys_returned = vm_obj.PutUsbScanCodes(self.usb_scan_code_spec)
self.change_detected = True
except vmodl.RuntimeFault as e:
self.module.fail_json(msg="Failed to send key %s to virtual machine due to %s" % (key_event, to_native(e.msg)))

sendkey_facts = self.get_sendkey_facts(vm_obj, num_keys_returned)
if num_keys_returned != self.num_keys_send:
results = {'changed': self.change_detected, 'failed': True, 'sendkey_facts': sendkey_facts}
else:
results = {'changed': self.change_detected, 'failed': False, 'sendkey_facts': sendkey_facts}

return results


def main():
argument_spec = vmware_argument_spec()
argument_spec.update(
name=dict(type='str'),
uuid=dict(type='str'),
folder=dict(type='str'),
datacenter=dict(type='str'),
esxi_hostname=dict(type='str'),
cluster=dict(type='str'),
keys_send=dict(type='list', default=[]),
string_send=dict(type='str')
)

module = AnsibleModule(argument_spec=argument_spec, required_one_of=[['name', 'uuid']])
pyv = PyVmomiHelper(module)
vm = pyv.get_vm()
if not vm:
module.fail_json(msg='Unable to find the specified virtual machine uuid: %s, name: %s '
% ((module.params.get('uuid')), (module.params.get('name'))))

result = pyv.send_key_to_vm(vm)
if result['failed']:
module.fail_json(**result)
else:
module.exit_json(**result)


if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions test/integration/targets/vmware_guest_sendkey/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
cloud/vcenter
shippable/vcenter/group1
needs/target/prepare_vmware_tests
Loading

0 comments on commit fc5c6d4

Please sign in to comment.