From 627ff30a6fc141777487aa61a4c8f196e290f375 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 17 Jun 2014 12:30:34 -0500 Subject: [PATCH 001/107] Add module replacer capability for powershell files. --- MANIFEST.in | 1 + lib/ansible/module_common.py | 19 ++++++++++++++-- lib/ansible/module_utils/powershell.ps1 | 30 +++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 lib/ansible/module_utils/powershell.ps1 diff --git a/MANIFEST.in b/MANIFEST.in index cd7d324d70..904c89f9db 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,7 @@ include examples/hosts include examples/ansible.cfg graft examples/playbooks include packaging/distutils/setup.py +include lib/ansible/module_common/*.ps1 recursive-include docs * recursive-include library * include Makefile diff --git a/lib/ansible/module_common.py b/lib/ansible/module_common.py index fa8be1a4a0..4bcaaff27b 100644 --- a/lib/ansible/module_common.py +++ b/lib/ansible/module_common.py @@ -29,6 +29,7 @@ from ansible import constants as C REPLACER = "#<>" REPLACER_ARGS = "\"<>\"" REPLACER_COMPLEX = "\"<>\"" +REPLACER_WINDOWS = "# POWERSHELL_COMMON" class ModuleReplacer(object): @@ -54,6 +55,10 @@ class ModuleReplacer(object): All modules are required to import at least basic, though there will also be other snippets. + + # POWERSHELL_COMMON will also map to + {{ include 'powershell.ps1' }} + """ # ****************************************************************************** @@ -97,6 +102,10 @@ class ModuleReplacer(object): if REPLACER in line: output.write(self.slurp(os.path.join(self.snippet_path, "basic.py"))) snippet_names.append('basic') + if REPLACER_WINDOWS in line: + ps_data = self.slurp(os.path.join(self.snippet_path, "powershell.ps1")) + output.write(ps_data) + snippet_names.append('powershell') elif line.startswith('from ansible.module_utils.'): tokens=line.split(".") import_error = False @@ -116,8 +125,14 @@ class ModuleReplacer(object): output.write(line) output.write("\n") - if len(snippet_names) > 0 and not 'basic' in snippet_names: - raise errors.AnsibleError("missing required import in %s: from ansible.module_utils.basic import *" % module_path) + if not module_path.endswith(".ps1"): + # Unixy modules + if len(snippet_names) > 0 and not 'basic' in snippet_names: + raise errors.AnsibleError("missing required import in %s: from ansible.module_utils.basic import *" % module_path) + else: + # Windows modules + if len(snippet_names) > 0 and not 'powershell' in snippet_names: + raise errors.AnsibleError("missing required import in %s: # POWERSHELL_COMMON" % module_path) return (output.getvalue(), module_style) diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 new file mode 100644 index 0000000000..34f80fcd47 --- /dev/null +++ b/lib/ansible/module_utils/powershell.ps1 @@ -0,0 +1,30 @@ + +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# Copyright (c), Michael DeHaan , 2014, and others +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + + From 5dcaa30476b03f3ea150053a13eb2864cce0ce45 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 17 Jun 2014 15:15:18 -0500 Subject: [PATCH 002/107] Add shell_plugins to abstract shell-specific functions out of runner, add winrm connection plugin, add initial Windows modules. --- lib/ansible/runner/__init__.py | 129 ++++----- lib/ansible/runner/action_plugins/assemble.py | 2 +- lib/ansible/runner/action_plugins/async.py | 2 +- lib/ansible/runner/action_plugins/copy.py | 6 +- lib/ansible/runner/action_plugins/fetch.py | 2 +- lib/ansible/runner/action_plugins/script.py | 13 +- lib/ansible/runner/action_plugins/template.py | 4 +- .../runner/action_plugins/unarchive.py | 2 +- lib/ansible/runner/connection.py | 5 +- .../runner/connection_plugins/winrm.py | 254 ++++++++++++++++++ lib/ansible/runner/shell_plugins/__init__.py | 0 lib/ansible/runner/shell_plugins/csh.py | 6 + lib/ansible/runner/shell_plugins/fish.py | 6 + .../runner/shell_plugins/powershell.py | 72 +++++ lib/ansible/runner/shell_plugins/sh.py | 71 +++++ lib/ansible/utils/plugins.py | 33 ++- library/windows/assemble.ps1 | 13 + library/windows/async_wrapper.ps1 | 13 + library/windows/command.ps1 | 13 + library/windows/copy.ps1 | 13 + library/windows/file.ps1 | 13 + library/windows/ping.ps1 | 17 ++ library/windows/slurp.ps1 | 33 +++ library/windows/stat.ps1 | 51 ++++ library/windows/win_ping | 87 ++++++ 25 files changed, 757 insertions(+), 103 deletions(-) create mode 100644 lib/ansible/runner/connection_plugins/winrm.py create mode 100644 lib/ansible/runner/shell_plugins/__init__.py create mode 100644 lib/ansible/runner/shell_plugins/csh.py create mode 100644 lib/ansible/runner/shell_plugins/fish.py create mode 100644 lib/ansible/runner/shell_plugins/powershell.py create mode 100644 lib/ansible/runner/shell_plugins/sh.py create mode 100644 library/windows/assemble.ps1 create mode 100644 library/windows/async_wrapper.ps1 create mode 100644 library/windows/command.ps1 create mode 100644 library/windows/copy.ps1 create mode 100644 library/windows/file.ps1 create mode 100644 library/windows/ping.ps1 create mode 100644 library/windows/slurp.ps1 create mode 100644 library/windows/stat.ps1 create mode 100644 library/windows/win_ping diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index adec55dc1b..4bc78bc3b1 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -167,7 +167,7 @@ class Runner(object): self.module_vars = utils.default(module_vars, lambda: {}) self.default_vars = utils.default(default_vars, lambda: {}) self.always_run = None - self.connector = connection.Connection(self) + self.connector = connection.Connector(self) self.conditional = conditional self.module_name = module_name self.forks = int(forks) @@ -198,7 +198,7 @@ class Runner(object): self.vault_pass = vault_pass self.no_log = no_log - if self.transport == 'smart': + if self.transport == 'smart': # FIXME # if the transport is 'smart' see if SSH can support ControlPersist if not use paramiko # 'smart' is the default since 1.2.1/1.3 cmd = subprocess.Popen(['ssh','-o','ControlPersist'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -275,7 +275,7 @@ class Runner(object): afo.flush() afo.close() - remote = os.path.join(tmp, name) + remote = conn.shell.join_path(tmp, name) try: conn.put_file(afile, remote) finally: @@ -284,32 +284,17 @@ class Runner(object): # ***************************************************** - def _compute_environment_string(self, inject=None): + def _compute_environment_string(self, conn, inject=None): # CCTODO: Changed this method signature ''' what environment variables to use when running the command? ''' - shell_type = inject.get('ansible_shell_type') - if not shell_type: - shell_type = os.path.basename(C.DEFAULT_EXECUTABLE) - - default_environment = dict( - LANG = C.DEFAULT_MODULE_LANG, - LC_CTYPE = C.DEFAULT_MODULE_LANG, - ) - + enviro = {} if self.environment: enviro = template.template(self.basedir, self.environment, inject, convert_bare=True) enviro = utils.safe_eval(enviro) if type(enviro) != dict: raise errors.AnsibleError("environment must be a dictionary, received %s" % enviro) - default_environment.update(enviro) - result = "" - for (k,v) in default_environment.iteritems(): - if shell_type in ('csh', 'fish'): - result = "env %s=%s %s" % (k, pipes.quote(unicode(v)), result) - else: - result = "%s=%s %s" % (k, pipes.quote(unicode(v)), result) - return result + return conn.shell.env_prefix(**enviro) # ***************************************************** @@ -425,7 +410,7 @@ class Runner(object): if self._late_needs_tmp_path(conn, tmp, module_style): tmp = self._make_tmp_path(conn) - remote_module_path = os.path.join(tmp, module_name) + remote_module_path = conn.shell.join_path(tmp, module_name) if (module_style != 'new' or async_jid is not None @@ -435,12 +420,11 @@ class Runner(object): or self.su): self._transfer_str(conn, tmp, module_name, module_data) - environment_string = self._compute_environment_string(inject) + environment_string = self._compute_environment_string(conn, inject) if "tmp" in tmp and ((self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root')): # deal with possible umask issues once sudo'ed to other user - cmd_chmod = "chmod a+r %s" % remote_module_path - self._low_level_exec_command(conn, cmd_chmod, tmp, sudoable=False) + self._remote_chmod(conn, 'a+r', remote_module_path) cmd = "" in_data = None @@ -468,8 +452,7 @@ class Runner(object): if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root'): # deal with possible umask issues once sudo'ed to other user - cmd_args_chmod = "chmod a+r %s" % argsfile - self._low_level_exec_command(conn, cmd_args_chmod, tmp, sudoable=False) + self._remote_chmod(conn, 'a+r', argsfile) if async_jid is None: cmd = "%s %s" % (remote_module_path, argsfile) @@ -487,14 +470,14 @@ class Runner(object): if not shebang: raise errors.AnsibleError("module is missing interpreter line") - - cmd = " ".join([environment_string.strip(), shebang.replace("#!","").strip(), cmd]) - cmd = cmd.strip() - + rm_tmp = None if "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp: if not self.sudo or self.su or self.sudo_user == 'root' or self.su_user == 'root': # not sudoing or sudoing to root, so can cleanup files in the same step - cmd = cmd + "; rm -rf %s >/dev/null 2>&1" % tmp + rm_tmp = tmp + + cmd = conn.shell.build_module_command(environment_string, shebang, cmd, rm_tmp) + cmd = cmd.strip() sudoable = True if module_name == "accelerate": @@ -511,7 +494,7 @@ class Runner(object): if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root'): # not sudoing to root, so maybe can't delete files as that other user # have to clean up temp files as original user in a second step - cmd2 = "rm -rf %s >/dev/null 2>&1" % tmp + cmd2 = conn.shell.remove(tmp, recurse=True) self._low_level_exec_command(conn, cmd2, tmp, sudoable=False) data = utils.parse_json(res['stdout']) @@ -776,7 +759,8 @@ class Runner(object): if not self.accelerate_port: self.accelerate_port = C.ACCELERATE_PORT - if actual_transport in [ 'paramiko', 'ssh', 'accelerate' ]: + # CCTODO: Any reason not to do this regardless of connection type, and let the connection plugin ignore it? + if True:#actual_transport in [ 'paramiko', 'ssh', 'accelerate', 'winrm' ]: actual_port = inject.get('ansible_ssh_port', port) # the delegated host may have different SSH port configured, etc @@ -818,6 +802,18 @@ class Runner(object): if delegate_to or host != actual_host: conn.delegate = host + default_shell = getattr(conn, 'default_shell', '') + shell_type = inject.get('ansible_shell_type') + if not shell_type: + if default_shell: + shell_type = default_shell + else: + shell_type = os.path.basename(C.DEFAULT_EXECUTABLE) + + shell_plugin = utils.plugins.shell_loader.get(shell_type) + if shell_plugin is None: + shell_plugin = utils.plugins.shell_loader.get('sh') + conn.shell = shell_plugin except errors.AnsibleConnectionFailed, e: result = dict(failed=True, msg="FAILED: %s" % str(e)) @@ -947,6 +943,9 @@ class Runner(object): executable=None, su=False, in_data=None): ''' execute a command string over SSH, return the output ''' + if not cmd: + return dict(stdout='', stderr='') + if executable is None: executable = C.DEFAULT_EXECUTABLE @@ -954,16 +953,11 @@ class Runner(object): su_user = self.su_user # compare connection user to (su|sudo)_user and disable if the same - if hasattr(conn, 'user'): - if (not su and conn.user == sudo_user) or (su and conn.user == su_user): - sudoable = False - su = False - else: - # assume connection type is local if no user attribute - this_user = getpass.getuser() - if (not su and this_user == sudo_user) or (su and this_user == su_user): - sudoable = False - su = False + # assume connection type is local if no user attribute + this_user = getattr(conn, 'user', getpass.getuser()) + if (not su and this_user == sudo_user) or (su and this_user == su_user): + sudoable = False + su = False if su: rc, stdin, stdout, stderr = conn.exec_command(cmd, @@ -997,26 +991,16 @@ class Runner(object): # ***************************************************** + def _remote_chmod(self, conn, mode, path, tmp, sudoable=False, su=False): + ''' issue a remote chmod command ''' + cmd = conn.shell.chmod(mode, path) + return self._low_level_exec_command(conn, cmd, tmp, sudoable=sudoable, su=su) + + # ***************************************************** + def _remote_md5(self, conn, tmp, path): ''' takes a remote md5sum without requiring python, and returns 1 if no file ''' - - path = pipes.quote(path) - # The following test needs to be SH-compliant. BASH-isms will - # not work if /bin/sh points to a non-BASH shell. - test = "rc=0; [ -r \"%s\" ] || rc=2; [ -f \"%s\" ] || rc=1; [ -d \"%s\" ] && echo 3 && exit 0" % ((path,) * 3) - md5s = [ - "(/usr/bin/md5sum %s 2>/dev/null)" % path, # Linux - "(/sbin/md5sum -q %s 2>/dev/null)" % path, # ? - "(/usr/bin/digest -a md5 %s 2>/dev/null)" % path, # Solaris 10+ - "(/sbin/md5 -q %s 2>/dev/null)" % path, # Freebsd - "(/usr/bin/md5 -n %s 2>/dev/null)" % path, # Netbsd - "(/bin/md5 -q %s 2>/dev/null)" % path, # Openbsd - "(/usr/bin/csum -h MD5 %s 2>/dev/null)" % path, # AIX - "(/bin/csum -h MD5 %s 2>/dev/null)" % path # AIX also - ] - - cmd = " || ".join(md5s) - cmd = "%s; %s || (echo \"${rc} %s\")" % (test, cmd, path) + cmd = conn.shell.md5(path) data = self._low_level_exec_command(conn, cmd, tmp, sudoable=True) data2 = utils.last_non_blank_line(data['stdout']) try: @@ -1039,17 +1023,16 @@ class Runner(object): def _make_tmp_path(self, conn): ''' make and return a temporary path on a remote box ''' - basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) - basetmp = os.path.join(C.DEFAULT_REMOTE_TMP, basefile) - if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root') and basetmp.startswith('$HOME'): - basetmp = os.path.join('/tmp', basefile) + use_system_tmp = False + if (self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root'): + use_system_tmp = True - cmd = 'mkdir -p %s' % basetmp + tmp_mode = None if self.remote_user != 'root' or ((self.sudo and self.sudo_user != 'root') or (self.su and self.su_user != 'root')): - cmd += ' && chmod a+rx %s' % basetmp - cmd += ' && echo %s' % basetmp + tmp_mode = 'a+rx' + cmd = conn.shell.mkdtemp(basefile, use_system_tmp, tmp_mode) result = self._low_level_exec_command(conn, cmd, None, sudoable=False) # error handling on this seems a little aggressive? @@ -1078,9 +1061,8 @@ class Runner(object): def _remove_tmp_path(self, conn, tmp_path): ''' Remove a tmp_path. ''' - if "-tmp-" in tmp_path: - cmd = "rm -rf %s >/dev/null 2>&1" % tmp_path + cmd = conn.shell.remove(tmp_path, recurse=True) self._low_level_exec_command(conn, cmd, None, sudoable=False) # If we have gotten here we have a working ssh configuration. # If ssh breaks we could leave tmp directories out on the remote system. @@ -1094,7 +1076,7 @@ class Runner(object): module_shebang, module_data ) = self._configure_module(conn, module_name, module_args, inject, complex_args) - module_remote_path = os.path.join(tmp, module_name) + module_remote_path = conn.shell.join_path(tmp, module_name) self._transfer_str(conn, tmp, module_name, module_data) @@ -1106,7 +1088,8 @@ class Runner(object): ''' find module and configure it ''' # Search module path(s) for named module. - module_path = utils.plugins.module_finder.find_plugin(module_name) + module_suffixes = getattr(conn, 'default_suffixes', None) + module_path = utils.plugins.module_finder.find_plugin(module_name, module_suffixes) if module_path is None: raise errors.AnsibleFileNotFound("module %s not found in %s" % (module_name, utils.plugins.module_finder.print_paths())) diff --git a/lib/ansible/runner/action_plugins/assemble.py b/lib/ansible/runner/action_plugins/assemble.py index d99d202e24..1a980c1df4 100644 --- a/lib/ansible/runner/action_plugins/assemble.py +++ b/lib/ansible/runner/action_plugins/assemble.py @@ -119,7 +119,7 @@ class ActionModule(object): # fix file permissions when the copy is done as a different user if self.runner.sudo and self.runner.sudo_user != 'root': - self.runner._low_level_exec_command(conn, "chmod a+r %s" % xfered, tmp) + self.runner._remote_chmod(conn, 'a+r', xfered, tmp) # run the copy module module_args = "%s src=%s dest=%s original_basename=%s" % (module_args, pipes.quote(xfered), pipes.quote(dest), pipes.quote(os.path.basename(src))) diff --git a/lib/ansible/runner/action_plugins/async.py b/lib/ansible/runner/action_plugins/async.py index ac0d6e8492..dc53d6fa6c 100644 --- a/lib/ansible/runner/action_plugins/async.py +++ b/lib/ansible/runner/action_plugins/async.py @@ -37,7 +37,7 @@ class ActionModule(object): tmp = self.runner._make_tmp_path(conn) (module_path, is_new_style, shebang) = self.runner._copy_module(conn, tmp, module_name, module_args, inject, complex_args=complex_args) - self.runner._low_level_exec_command(conn, "chmod a+rx %s" % module_path, tmp) + self.runner._remote_chmod(conn, 'a+rx', module_path, tmp) return self.runner._execute_module(conn, tmp, 'async_wrapper', module_args, async_module=module_path, diff --git a/lib/ansible/runner/action_plugins/copy.py b/lib/ansible/runner/action_plugins/copy.py index df5266c4c0..d84b3f550e 100644 --- a/lib/ansible/runner/action_plugins/copy.py +++ b/lib/ansible/runner/action_plugins/copy.py @@ -169,7 +169,7 @@ class ActionModule(object): # This is kind of optimization - if user told us destination is # dir, do path manipulation right away, otherwise we still check # for dest being a dir via remote call below. - if dest.endswith("/"): + if dest.endswith("/"): # CCTODO: Fixme for powershell dest_file = os.path.join(dest, source_rel) else: dest_file = dest @@ -186,7 +186,7 @@ class ActionModule(object): return ReturnData(conn=conn, result=result) else: # Append the relative source location to the destination and retry remote_md5. - dest_file = os.path.join(dest, source_rel) + dest_file = os.path.join(dest, source_rel) # CCTODO remote_md5 = self.runner._remote_md5(conn, tmp_path, dest_file) if remote_md5 != '1' and not force: @@ -228,7 +228,7 @@ class ActionModule(object): # fix file permissions when the copy is done as a different user if self.runner.sudo and self.runner.sudo_user != 'root' and not raw: - self.runner._low_level_exec_command(conn, "chmod a+r %s" % tmp_src, tmp_path) + self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp_path) if raw: # Continue to next iteration if raw is defined. diff --git a/lib/ansible/runner/action_plugins/fetch.py b/lib/ansible/runner/action_plugins/fetch.py index 205023fad9..2d7f7e974d 100644 --- a/lib/ansible/runner/action_plugins/fetch.py +++ b/lib/ansible/runner/action_plugins/fetch.py @@ -59,7 +59,7 @@ class ActionModule(object): source = os.path.expanduser(source) if flat: - if dest.endswith("/"): + if dest.endswith("/"): # CCTODO # if the path ends with "/", we'll use the source filename as the # destination filename base = os.path.basename(source) diff --git a/lib/ansible/runner/action_plugins/script.py b/lib/ansible/runner/action_plugins/script.py index 6951d6154a..2fac86a3f2 100644 --- a/lib/ansible/runner/action_plugins/script.py +++ b/lib/ansible/runner/action_plugins/script.py @@ -106,7 +106,8 @@ class ActionModule(object): # transfer the file to a remote tmp location source = source.replace('\x00', '') # why does this happen here? args = args.replace('\x00', '') # why does this happen here? - tmp_src = os.path.join(tmp, os.path.basename(source)) + #tmp_src = os.path.join(tmp, os.path.basename(source)) # CCTODO + tmp_src = conn.shell.join_path(tmp, os.path.basename(source)) tmp_src = tmp_src.replace('\x00', '') conn.put_file(source, tmp_src) @@ -115,14 +116,14 @@ class ActionModule(object): # set file permissions, more permisive when the copy is done as a different user if ((self.runner.sudo and self.runner.sudo_user != 'root') or (self.runner.su and self.runner.su_user != 'root')): - cmd_args_chmod = "chmod a+rx %s" % tmp_src + chmod_mode = 'a+rx' sudoable = False else: - cmd_args_chmod = "chmod +rx %s" % tmp_src - self.runner._low_level_exec_command(conn, cmd_args_chmod, tmp, sudoable=sudoable, su=self.runner.su) + chmod_mode = '+rx' + self.runner._remote_chmod(conn, chmod_mode, tmp_src, tmp, sudoable=sudoable, su=self.runner.su) # add preparation steps to one ssh roundtrip executing the script - env_string = self.runner._compute_environment_string(inject) + env_string = self.runner._compute_environment_string(conn, inject) module_args = env_string + tmp_src + ' ' + args handler = utils.plugins.action_loader.get('raw', self.runner) @@ -130,7 +131,7 @@ class ActionModule(object): # clean up after if "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES: - self.runner._low_level_exec_command(conn, 'rm -rf %s >/dev/null 2>&1' % tmp, tmp) + self.runner._remove_tmp_path(conn, tmp) result.result['changed'] = True diff --git a/lib/ansible/runner/action_plugins/template.py b/lib/ansible/runner/action_plugins/template.py index 96d8f97a3a..774a2be6ff 100644 --- a/lib/ansible/runner/action_plugins/template.py +++ b/lib/ansible/runner/action_plugins/template.py @@ -79,7 +79,7 @@ class ActionModule(object): source = utils.path_dwim(self.runner.basedir, source) - if dest.endswith("/"): + if dest.endswith("/"): # CCTODO base = os.path.basename(source) dest = os.path.join(dest, base) @@ -114,7 +114,7 @@ class ActionModule(object): # fix file permissions when the copy is done as a different user if self.runner.sudo and self.runner.sudo_user != 'root': - self.runner._low_level_exec_command(conn, "chmod a+r %s" % xfered, tmp) + self.runner._remote_chmod(conn, 'a+r', xfered, tmp) # run the copy module module_args = "%s src=%s dest=%s original_basename=%s" % (module_args, pipes.quote(xfered), pipes.quote(dest), pipes.quote(os.path.basename(source))) diff --git a/lib/ansible/runner/action_plugins/unarchive.py b/lib/ansible/runner/action_plugins/unarchive.py index c943cab514..be0070fca1 100644 --- a/lib/ansible/runner/action_plugins/unarchive.py +++ b/lib/ansible/runner/action_plugins/unarchive.py @@ -77,7 +77,7 @@ class ActionModule(object): # fix file permissions when the copy is done as a different user if copy: if self.runner.sudo and self.runner.sudo_user != 'root': - self.runner._low_level_exec_command(conn, "chmod a+r %s" % tmp_src, tmp) + self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp) module_args = "%s src=%s original_basename=%s" % (module_args, pipes.quote(tmp_src), pipes.quote(os.path.basename(source))) else: module_args = "%s original_basename=%s" % (module_args, pipes.quote(os.path.basename(source))) diff --git a/lib/ansible/runner/connection.py b/lib/ansible/runner/connection.py index ad49d1e0b7..d7def56fbf 100644 --- a/lib/ansible/runner/connection.py +++ b/lib/ansible/runner/connection.py @@ -25,18 +25,15 @@ import ansible.constants as C import os import os.path -class Connection(object): +class Connector(object): ''' Handles abstract connections to remote hosts ''' def __init__(self, runner): self.runner = runner def connect(self, host, port, user, password, transport, private_key_file): - conn = None conn = utils.plugins.connection_loader.get(transport, self.runner, host, port, user=user, password=password, private_key_file=private_key_file) if conn is None: raise AnsibleError("unsupported connection type: %s" % transport) self.active = conn.connect() return self.active - - diff --git a/lib/ansible/runner/connection_plugins/winrm.py b/lib/ansible/runner/connection_plugins/winrm.py new file mode 100644 index 0000000000..10a9872fa4 --- /dev/null +++ b/lib/ansible/runner/connection_plugins/winrm.py @@ -0,0 +1,254 @@ +# (c) 2014, Chris Church +# +# This file is (not yet) 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 . + +from __future__ import absolute_import + +import base64 +import hashlib +import imp +import os +import re +import shlex +import traceback +import urlparse +from ansible import errors +from ansible import utils +from ansible.callbacks import vvv, vvvv + +try: + from winrm import Response + from winrm.exceptions import WinRMTransportError + from winrm.protocol import Protocol +except ImportError: + raise errors.AnsibleError("winrm is not installed") + +# When running with unmodified Ansible (1.6.x), load local hacks. +try: + _winrm_hacks = imp.load_source('_winrm_hacks', os.path.join(os.path.dirname(__file__), '_winrm_hacks.py')) +except (ImportError, IOError): + _winrm_hacks = None + +_winrm_cache = { + # 'user:pwhash@host:port': +} + +class Connection(object): + '''WinRM connections over HTTP/HTTPS.''' + + def __init__(self, runner, host, port, user, password, *args, **kwargs): + self.runner = runner + self.host = host + self.port = port + self.user = user + self.password = password + self.has_pipelining = False + self.default_shell = 'powershell' + self.default_suffixes = ['.ps1', ''] + self.protocol = None + self.shell_id = None + self.delegate = None + if _winrm_hacks: + _winrm_hacks.patch_module_finder(self) + + def _winrm_connect(self): + ''' + Establish a WinRM connection over HTTP/HTTPS. + ''' + if _winrm_hacks: + port = _winrm_hacks.get_port(self) + else: + port = self.port or 5986 + vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \ + (self.user, port, self.host), host=self.host) + netloc = '%s:%d' % (self.host, port) + cache_key = '%s:%s@%s:%d' % (self.user, hashlib.md5(self.password).hexdigest(), self.host, port) + if cache_key in _winrm_cache: + vvvv('WINRM REUSE EXISTING CONNECTION: %s' % cache_key, host=self.host) + return _winrm_cache[cache_key] + transport_schemes = [('plaintext', 'https'), ('plaintext', 'http')] # FIXME: ssl/kerberos + if port == 5985: + transport_schemes = reversed(transport_schemes) + exc = None + for transport, scheme in transport_schemes: + endpoint = urlparse.urlunsplit((scheme, netloc, '/wsman', '', '')) + vvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), + host=self.host) + protocol = Protocol(endpoint, transport=transport, + username=self.user, password=self.password) + try: + protocol.send_message('') + _winrm_cache[cache_key] = protocol + return protocol + except WinRMTransportError, exc: + err_msg = str(exc.args[0]) + if re.search(r'Operation\s+?timed\s+?out', err_msg, re.I): + raise + m = re.search(r'Code\s+?(\d{3})', err_msg) + if m: + code = int(m.groups()[0]) + if code == 411: + _winrm_cache[cache_key] = protocol + return protocol + vvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self.host) + continue + # FIXME: Cache connection!!! + if exc: + raise exc + + def _winrm_escape(self, value, include_vars=False): + ''' + Return value escaped for use in PowerShell command. + ''' + # http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences + # http://stackoverflow.com/questions/764360/a-list-of-string-replacements-in-python + subs = [('\n', '`n'), ('\r', '`r'), ('\t', '`t'), ('\a', '`a'), + ('\b', '`b'), ('\f', '`f'), ('\v', '`v'), ('"', '`"'), + ('\'', '`\''), ('`', '``'), ('\x00', '`0')] + if include_vars: + subs.append(('$', '`$')) + pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs) + substs = [s for p, s in subs] + replace = lambda m: substs[m.lastindex - 1] + return re.sub(pattern, replace, value) + + def _winrm_get_script_cmd(self, script): + ''' + Convert a PowerShell script to a single base64-encoded command. + ''' + vvvv('WINRM SCRIPT: %s' % script, host=self.host) + encoded_script = base64.b64encode(script.encode('utf-16-le')) + return ['PowerShell', '-NoProfile', '-NonInteractive', + '-EncodedCommand', encoded_script] + + def _winrm_exec(self, command, args): + vvvv("WINRM EXEC %r %r" % (command, args), host=self.host) + if not self.protocol: + self.protocol = self._winrm_connect() + if not self.shell_id: + self.shell_id = self.protocol.open_shell() + command_id = None + try: + command_id = self.protocol.run_command(self.shell_id, command, args) + response = Response(self.protocol.get_command_output(self.shell_id, command_id)) + vvvv('WINRM RESULT %r' % response, host=self.host) + vvvv('WINRM STDERR %s' % response.std_err, host=self.host) + return response + finally: + if command_id: + self.protocol.cleanup_command(self.shell_id, command_id) + + def connect(self): + if not _winrm_hacks: + if not self.protocol: + self.protocol = self._winrm_connect() + # When using hacks, connect lazily on first command, to allow for + # runner to set self.delegate, needed if actual host vs. host name are + # different. + return self + + def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None): + cmd = cmd.encode('utf-8') + vvv("EXEC %s" % cmd, host=self.host) + cmd_parts = shlex.split(cmd, posix=False) + vvvv("WINRM PARTS %r" % cmd_parts, host=self.host) + # For script/raw support. + if len(cmd_parts) == 1 and cmd_parts[0].lower().endswith('.ps1'): + cmd_parts = ['PowerShell', '-ExecutionPolicy', 'Unrestricted', '-File', cmd_parts[0]] + if _winrm_hacks: + cmd_parts = _winrm_hacks.filter_cmd_parts(self, cmd_parts) + if not cmd_parts: + vvv('WINRM NOOP') + return (0, '', '', '') + try: + result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) + except Exception, e: + traceback.print_exc() + raise errors.AnsibleError("failed to exec cmd %s" % cmd) + return (result.status_code, '', result.std_out.encode('utf-8'), result.std_err.encode('utf-8')) + + def put_file(self, in_path, out_path): + if _winrm_hacks: + out_path = _winrm_hacks.fix_slashes(out_path) + vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) + if not os.path.exists(in_path): + raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) + buffer_size = 1024 # FIXME: Find max size or optimize. + with open(in_path) as in_file: + in_size = os.path.getsize(in_path) + for offset in xrange(0, in_size, buffer_size): + try: + out_data = in_file.read(buffer_size) + if offset == 0: + if out_data.lower().startswith('#!powershell') and not out_path.lower().endswith('.ps1'): + out_path = out_path + '.ps1' + b64_data = base64.b64encode(out_data) + script = ''' + $bufferSize = %d; + $stream = [System.IO.File]::OpenWrite("%s"); + $stream.Seek(%d, [System.IO.SeekOrigin]::Begin) | Out-Null; + $data = "%s"; + $buffer = [System.Convert]::FromBase64String($data); + $stream.Write($buffer, 0, $buffer.length) | Out-Null; + $stream.SetLength(%d) | Out-Null; + $stream.Close() | Out-Null; + ''' % (buffer_size, self._winrm_escape(out_path), offset, b64_data, in_size) + cmd_parts = self._winrm_get_script_cmd(script) + result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) + if result.status_code != 0: + raise RuntimeError(result.std_err.encode('utf-8')) + script = u'' + except Exception: # IOError? + traceback.print_exc() + raise errors.AnsibleError("failed to transfer file to %s" % out_path) + + def fetch_file(self, in_path, out_path): + if _winrm_hacks: + in_path = _winrm_hacks.fix_slashes(in_path) + out_path = out_path.replace('\\', '/') + vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) + buffer_size = 2**20 # 1MB chunks + if not os.path.exists(os.path.dirname(out_path)): + os.makedirs(os.path.dirname(out_path)) + with open(out_path, 'wb') as out_file: + offset = 0 + while True: + try: + script = ''' + $bufferSize = %d; + $stream = [System.IO.File]::OpenRead("%s"); + $stream.Seek(%d, [System.IO.SeekOrigin]::Begin) | Out-Null; + $buffer = New-Object Byte[] $bufferSize; + $bytesRead = $stream.Read($buffer, 0, $bufferSize); + $bytes = $buffer[0..($bytesRead-1)]; + [System.Convert]::ToBase64String($bytes); + $stream.Close() | Out-Null; + ''' % (buffer_size, self._winrm_escape(in_path), offset) + cmd_parts = self._winrm_get_script_cmd(script) + result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) + data = base64.b64decode(result.std_out.strip()) + out_file.write(data) + if len(data) < buffer_size: + break + offset += len(data) + except Exception: # IOError? + traceback.print_exc() + raise errors.AnsibleError("failed to transfer file to %s" % out_path) + + def close(self): + if self.protocol and self.shell_id: + self.protocol.close_shell(self.shell_id) + self.shell_id = None diff --git a/lib/ansible/runner/shell_plugins/__init__.py b/lib/ansible/runner/shell_plugins/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/runner/shell_plugins/csh.py b/lib/ansible/runner/shell_plugins/csh.py new file mode 100644 index 0000000000..e52ad55528 --- /dev/null +++ b/lib/ansible/runner/shell_plugins/csh.py @@ -0,0 +1,6 @@ +from ansible.runner.shell_plugins.sh import ShellModule as ShModule + +class ShellModule(ShModule): + + def env_prefix(self, **kwargs): + return 'env %s' % super(ShellModule, self).env_prefix(**kwargs) diff --git a/lib/ansible/runner/shell_plugins/fish.py b/lib/ansible/runner/shell_plugins/fish.py new file mode 100644 index 0000000000..e52ad55528 --- /dev/null +++ b/lib/ansible/runner/shell_plugins/fish.py @@ -0,0 +1,6 @@ +from ansible.runner.shell_plugins.sh import ShellModule as ShModule + +class ShellModule(ShModule): + + def env_prefix(self, **kwargs): + return 'env %s' % super(ShellModule, self).env_prefix(**kwargs) diff --git a/lib/ansible/runner/shell_plugins/powershell.py b/lib/ansible/runner/shell_plugins/powershell.py new file mode 100644 index 0000000000..185116a406 --- /dev/null +++ b/lib/ansible/runner/shell_plugins/powershell.py @@ -0,0 +1,72 @@ +import base64 +import os +import re +import random +import shlex +import time + +class ShellModule(object): + + def __init__(self): + pass + + def _escape(self, value, include_vars=False): + ''' + Return value escaped for use in PowerShell command. + ''' + # http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences + # http://stackoverflow.com/questions/764360/a-list-of-string-replacements-in-python + subs = [('\n', '`n'), ('\r', '`r'), ('\t', '`t'), ('\a', '`a'), + ('\b', '`b'), ('\f', '`f'), ('\v', '`v'), ('"', '`"'), + ('\'', '`\''), ('`', '``'), ('\x00', '`0')] + if include_vars: + subs.append(('$', '`$')) + pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs) + substs = [s for p, s in subs] + replace = lambda m: substs[m.lastindex - 1] + return re.sub(pattern, replace, value) + + def _get_script_cmd(self, script): + ''' + Convert a PowerShell script to a single base64-encoded command. + ''' + encoded_script = base64.b64encode(script.encode('utf-16-le')) + return ' '.join(['PowerShell', '-NoProfile', '-NonInteractive', + '-EncodedCommand', encoded_script]) + + def env_prefix(self, **kwargs): + return '' + + def join_path(self, *args): + return os.path.join(*args).replace('/', '\\') + + def chmod(self, mode, path): + return '' + + def remove(self, path, recurse=False): + path = self._escape(path) + if recurse: + return self._get_script_cmd('''Remove-Item "%s" -Force -Recurse;''' % path) + else: + return self._get_script_cmd('''Remove-Item "%s" -Force;''' % path) + + def mkdtemp(self, basefile=None, system=False, mode=None): + if not basefile: + basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) + basefile = self._escape(basefile) + # FIXME: Support system temp path! + return self._get_script_cmd('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName;''' % basefile) + + def md5(self, path): + path = self._escape(path) + return self._get_script_cmd('''(Get-FileHash -Path "%s" -Algorithm MD5).Hash.ToLower();''' % path) + + def build_module_command(self, env_string, shebang, cmd, rm_tmp=None): + cmd_parts = shlex.split(cmd, posix=False) + if not cmd_parts[0].lower().endswith('.ps1'): + cmd_parts[0] = '%s.ps1' % cmd_parts[0] + cmd_parts = ['PowerShell', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Unrestricted', '-File'] + ['"%s"' % x for x in cmd_parts] + script = ' '.join(cmd_parts) + if rm_tmp: + script = '%s; Remove-Item "%s" -Force -Recurse;' % (script, self._escape(rm_tmp)) + return self._get_script_cmd(script) diff --git a/lib/ansible/runner/shell_plugins/sh.py b/lib/ansible/runner/shell_plugins/sh.py new file mode 100644 index 0000000000..9634f97205 --- /dev/null +++ b/lib/ansible/runner/shell_plugins/sh.py @@ -0,0 +1,71 @@ + +import os +import pipes +import ansible.constants as C + +class ShellModule(object): + + def __init__(self): + pass + + def env_prefix(self, **kwargs): + '''Build command prefix with environment variables.''' + env = dict( + LANG = C.DEFAULT_MODULE_LANG, + LC_CTYPE = C.DEFAULT_MODULE_LANG, + ) + env.update(kwargs) + return ' '.join(['%s=%s' % (k, pipes.quote(unicode(v))) for k,v in env.items()]) + + def join_path(self, *args): + return os.path.join(*args) + + def chmod(self, mode, path): + path = pipes.quote(path) + return 'chmod %s %s' % (mode, path) + + def remove(self, path, recurse=False): + path = pipes.quote(path) + if recurse: + return "rm -rf %s >/dev/null 2>&1" % path + else: + return "rm -f %s >/dev/null 2>&1" % path + + def mkdtemp(self, basefile=None, system=False, mode=None): + if not basefile: + basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) + basetmp = self.join_path(C.DEFAULT_REMOTE_TMP, basefile) + if system and basetmp.startswith('$HOME'): + basetmp = self.join_path('/tmp', basefile) + cmd = 'mkdir -p %s' % basetmp + if mode: + cmd += ' && chmod %s %s' % (mode, basetmp) + cmd += ' && echo %s' % basetmp + return cmd + + def md5(self, path): + path = pipes.quote(path) + # The following test needs to be SH-compliant. BASH-isms will + # not work if /bin/sh points to a non-BASH shell. + test = "rc=0; [ -r \"%s\" ] || rc=2; [ -f \"%s\" ] || rc=1; [ -d \"%s\" ] && echo 3 && exit 0" % ((path,) * 3) + md5s = [ + "(/usr/bin/md5sum %s 2>/dev/null)" % path, # Linux + "(/sbin/md5sum -q %s 2>/dev/null)" % path, # ? + "(/usr/bin/digest -a md5 %s 2>/dev/null)" % path, # Solaris 10+ + "(/sbin/md5 -q %s 2>/dev/null)" % path, # Freebsd + "(/usr/bin/md5 -n %s 2>/dev/null)" % path, # Netbsd + "(/bin/md5 -q %s 2>/dev/null)" % path, # Openbsd + "(/usr/bin/csum -h MD5 %s 2>/dev/null)" % path, # AIX + "(/bin/csum -h MD5 %s 2>/dev/null)" % path # AIX also + ] + + cmd = " || ".join(md5s) + cmd = "%s; %s || (echo \"${rc} %s\")" % (test, cmd, path) + return cmd + + def build_module_command(self, env_string, shebang, cmd, rm_tmp=None): + cmd_parts = [env_string.strip(), shebang.replace("#!", "").strip(), cmd] + new_cmd = " ".join(cmd_parts) + if rm_tmp: + new_cmd = '%s; rm -rf %s >/dev/null 2>&1' % (new_cmd, rm_tmp) + return new_cmd diff --git a/lib/ansible/utils/plugins.py b/lib/ansible/utils/plugins.py index 22d74c185a..3e2d15ded8 100644 --- a/lib/ansible/utils/plugins.py +++ b/lib/ansible/utils/plugins.py @@ -139,21 +139,25 @@ class PluginLoader(object): if directory not in self._extra_dirs: self._extra_dirs.append(directory) - def find_plugin(self, name): + def find_plugin(self, name, suffixes=None): ''' Find a plugin named name ''' - if name in self._plugin_path_cache: - return self._plugin_path_cache[name] + if not suffixes: + if self.class_name: + suffixes = ['.py'] + else: + suffixes = [''] - suffix = ".py" - if not self.class_name: - suffix = "" + for suffix in suffixes: + full_name = '%s%s' % (name, suffix) + if full_name in self._plugin_path_cache: + return self._plugin_path_cache[full_name] - for i in self._get_paths(): - path = os.path.join(i, "%s%s" % (name, suffix)) - if os.path.isfile(path): - self._plugin_path_cache[name] = path - return path + for i in self._get_paths(): + path = os.path.join(i, full_name) + if os.path.isfile(path): + self._plugin_path_cache[full_name] = path + return path return None @@ -212,6 +216,13 @@ connection_loader = PluginLoader( aliases={'paramiko': 'paramiko_ssh'} ) +shell_loader = PluginLoader( + 'ShellModule', + 'ansible.runner.shell_plugins', + 'shell_plugins', + 'shell_plugins', +) + module_finder = PluginLoader( '', '', diff --git a/library/windows/assemble.ps1 b/library/windows/assemble.ps1 new file mode 100644 index 0000000000..77385b5610 --- /dev/null +++ b/library/windows/assemble.ps1 @@ -0,0 +1,13 @@ +#!powershell +# WANT_JSON + +If ($args.Length -gt 0) +{ + $params = Get-Content $args[0] | ConvertFrom-Json; +} + +$data = 'FIXME'; + +$result = '{}' | ConvertFrom-Json; +$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +echo $result | ConvertTo-Json; diff --git a/library/windows/async_wrapper.ps1 b/library/windows/async_wrapper.ps1 new file mode 100644 index 0000000000..77385b5610 --- /dev/null +++ b/library/windows/async_wrapper.ps1 @@ -0,0 +1,13 @@ +#!powershell +# WANT_JSON + +If ($args.Length -gt 0) +{ + $params = Get-Content $args[0] | ConvertFrom-Json; +} + +$data = 'FIXME'; + +$result = '{}' | ConvertFrom-Json; +$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +echo $result | ConvertTo-Json; diff --git a/library/windows/command.ps1 b/library/windows/command.ps1 new file mode 100644 index 0000000000..77385b5610 --- /dev/null +++ b/library/windows/command.ps1 @@ -0,0 +1,13 @@ +#!powershell +# WANT_JSON + +If ($args.Length -gt 0) +{ + $params = Get-Content $args[0] | ConvertFrom-Json; +} + +$data = 'FIXME'; + +$result = '{}' | ConvertFrom-Json; +$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +echo $result | ConvertTo-Json; diff --git a/library/windows/copy.ps1 b/library/windows/copy.ps1 new file mode 100644 index 0000000000..77385b5610 --- /dev/null +++ b/library/windows/copy.ps1 @@ -0,0 +1,13 @@ +#!powershell +# WANT_JSON + +If ($args.Length -gt 0) +{ + $params = Get-Content $args[0] | ConvertFrom-Json; +} + +$data = 'FIXME'; + +$result = '{}' | ConvertFrom-Json; +$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +echo $result | ConvertTo-Json; diff --git a/library/windows/file.ps1 b/library/windows/file.ps1 new file mode 100644 index 0000000000..77385b5610 --- /dev/null +++ b/library/windows/file.ps1 @@ -0,0 +1,13 @@ +#!powershell +# WANT_JSON + +If ($args.Length -gt 0) +{ + $params = Get-Content $args[0] | ConvertFrom-Json; +} + +$data = 'FIXME'; + +$result = '{}' | ConvertFrom-Json; +$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +echo $result | ConvertTo-Json; diff --git a/library/windows/ping.ps1 b/library/windows/ping.ps1 new file mode 100644 index 0000000000..02560873f5 --- /dev/null +++ b/library/windows/ping.ps1 @@ -0,0 +1,17 @@ +#!powershell +# WANT_JSON + +If ($args.Length -gt 0) +{ + $params = Get-Content $args[0] | ConvertFrom-Json; +} + +$data = 'pong'; +If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'data') +{ + $data = $params.data; +} + +$result = '{}' | ConvertFrom-Json; +$result | Add-Member -MemberType NoteProperty -Name ping -Value $data; +echo $result | ConvertTo-Json; diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 new file mode 100644 index 0000000000..93a698491f --- /dev/null +++ b/library/windows/slurp.ps1 @@ -0,0 +1,33 @@ +#!powershell +# WANT_JSON + +$params = '{}' | ConvertFrom-Json; +If ($args.Length -gt 0) +{ + $params = Get-Content $args[0] | ConvertFrom-Json; +} + +$src = ''; +If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'src') +{ + $src = $params.src; +} +Else +{ + If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'path') + { + $src = $params.path; + } +} +If (-not $src) +{ + +} + +$bytes = [System.IO.File]::ReadAllBytes($src); +$content = [System.Convert]::ToBase64String($bytes); + +$result = '{}' | ConvertFrom-Json; +$result | Add-Member -MemberType NoteProperty -Name content -Value $content; +$result | Add-Member -MemberType NoteProperty -Name encoding -Value 'base64'; +echo $result | ConvertTo-Json; diff --git a/library/windows/stat.ps1 b/library/windows/stat.ps1 new file mode 100644 index 0000000000..f779b2531e --- /dev/null +++ b/library/windows/stat.ps1 @@ -0,0 +1,51 @@ +#!powershell +# WANT_JSON + +$params = '{}' | ConvertFrom-Json; +If ($args.Length -gt 0) +{ + $params = Get-Content $args[0] | ConvertFrom-Json; +} + +$path = ''; +If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'path') +{ + $path = $params.path; +} + +$get_md5 = $TRUE; +If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'get_md5') +{ + $get_md5 = $params.get_md5; +} + +$stat = '{}' | ConvertFrom-Json; +If (Test-Path $path) +{ + $stat | Add-Member -MemberType NoteProperty -Name exists -Value $TRUE; + $info = Get-Item $path; + If ($info.Directory) # Only files have the .Directory attribute. + { + $stat | Add-Member -MemberType NoteProperty -Name isdir -Value $FALSE; + $stat | Add-Member -MemberType NoteProperty -Name size -Value $info.Length; + } + Else + { + $stat | Add-Member -MemberType NoteProperty -Name isdir -Value $TRUE; + } +} +Else +{ + $stat | Add-Member -MemberType NoteProperty -Name exists -Value $FALSE; +} + +If ($get_md5 -and $stat.exists -and -not $stat.isdir) +{ + $path_md5 = (Get-FileHash -Path $path -Algorithm MD5).Hash.ToLower(); + $stat | Add-Member -MemberType NoteProperty -Name md5 -Value $path_md5; +} + +$result = '{}' | ConvertFrom-Json; +$result | Add-Member -MemberType NoteProperty -Name stat -Value $stat; +$result | Add-Member -MemberType NoteProperty -Name changed -Value $FALSE; +echo $result | ConvertTo-Json; diff --git a/library/windows/win_ping b/library/windows/win_ping new file mode 100644 index 0000000000..f64134454d --- /dev/null +++ b/library/windows/win_ping @@ -0,0 +1,87 @@ +#!powershell +# WANT_JSON + +If ($args.Length -gt 0) +{ + $params = Get-Content $args[0] | ConvertFrom-Json; +} + +$data = 'pong'; +If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'data') +{ + $data = $params.data; +} + +$result = '{}' | ConvertFrom-Json; +$result | Add-Member -MemberType NoteProperty -Name ping -Value $data; +echo $result | ConvertTo-Json; + +# _______ _ _ +# |__ __| | (_) +# | | | |__ _ ___ +# | | | '_ \| / __| +# | | | | | | \__ \ +# __|_| |_| |_|_|___/ +# |_ _| +# | | ___ +# | | / __| +# _| |_\__ \ +# |___/\|___/ +# / \ +# / /\ \ +# / ____ \ +# /_/ \_\ +# | | +# | | __ _ _ __ __ _ ___ +# | | / _` | '__/ _` |/ _ \ +# | |___| (_| | | | (_| | __/ +# |______\__,_|_| \__, |\___| +# __/ | +# ____ _ |___/ +# | _ \| | | | +# | |_) | | ___ ___| | __ +# | _ <| |/ _ \ / __| |/ / +# | |_) | | (_) | (__| < +# |____/|_|\___/ \___|_|\_\ +# / __ \ / _| +# | | | | |_ +# | | | | _| +# | |__| | | +# \____/|_| __ __ +# / ____| | / _|/ _| +# | (___ | |_ _ _| |_| |_ +# \___ \| __| | | | _| _| +# ____) | |_| |_| | | | | +# |_____/ \__|\__,_|_| |_| +# | | | | +# | |_ _ ___| |_ +# _ | | | | / __| __| +# | |__| | |_| \__ \ |_ +# \____/_\__,_|___/\__| +# |__ __| +# | | ___ +# | |/ _ \ +# | | (_) | +# __|_|\___/ _ +# | \/ | | | +# | \ / | __ _| | _____ +# | |\/| |/ _` | |/ / _ \ +# | | | | (_| | < __/ +# |_|__|_|\__,_|_|\_\___| +# |__ __| | +# | | | |__ ___ +# | | | '_ \ / _ \ +# | | | | | | __/ +# __|_|_ |_| |_|\___| +# | ____(_) | +# | |__ _| | ___ +# | __| | | |/ _ \ +# | | | | | __/ +# |_|__ |_|_|\___| +# | _ \(_) +# | |_) |_ __ _ __ _ ___ _ __ +# | _ <| |/ _` |/ _` |/ _ \ '__| +# | |_) | | (_| | (_| | __/ | +# |____/|_|\__, |\__, |\___|_| +# __/ | __/ | +# |___/ |___/ From ab10d91881a14f1c2e602cecd4772f18b3e75024 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 17 Jun 2014 13:41:08 -0500 Subject: [PATCH 003/107] Add start of introductory windows documentation. --- docsite/rst/intro_windows.rst | 136 ++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 docsite/rst/intro_windows.rst diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst new file mode 100644 index 0000000000..21c28ad1f6 --- /dev/null +++ b/docsite/rst/intro_windows.rst @@ -0,0 +1,136 @@ +Windows Support +=============== + +.. contents:: Topics + +.. _windows_how_does_it_work: + +Windows: How Does It Work +`````````````````````````` + +Ansible manages Linux/Unix machines using SSH by default. + +Starting in version 1.7, Ansible also contains support for managing Windows machines. + +Ansible will still run from a Linux guest, and uses the "winrm" Python module to talk to remote hosts. + +.. _windows_installing: + +Installing +`````````` + +On a Linux control machine:: + + pip install http://github.com/diyan/pywinrm/archive/master.zip#egg=pywinrm + +.. _windows_inventory: + +Inventory +````````` + +Ansible's windows support will rely on a few standard variables to indicate the username, password, and connection type (windows) +of the remote hosts:: + + + [windows] + windoze + + [windows:vars] + ansible_connection=winrm + ansible_ssh_port=5986 # Default for HTTPS, HTTP uses 5985 + +In group_vars/windows.yml, define the following inventory variables:: + + ansible-vault edit group_vars/windows.yml + + ansible_ssh_user: Administrator + ansible_ssh_pass: SekritPasswordGoesHere + ansible_ssh_port: 5986 + ansible_connection: winrm + +When using your playbook, don't forget to specify --ask-vault-pass to provide the password to unlock the file. + +Test your configuration like so, by trying to contact your Windows nodes. Note this is not an ICMP ping, but tests the Ansible +communication channel that leverages Windows remoting:: + + ansible windows [-i inventory] -m ping --ask-vault-pass + +.. _windows_what_modules_are_available: + +What modules are available +`````````````````````````` + +Most of the Ansible modules in core Ansible are written for a combination of Linux/Unix machines and arbitrary web services, though there are various +Windows modules as listed in the "windows" subcategory of the Ansible module index. + +Browse this index to see what is available. + +In many cases, it may not be neccessary to even write or use an Ansible module. + +In particular, the "win_script" module can be used to run arbitrary powershell scripts, allowing Windows administrators familiar with powershell a very +native way to do things, as in the following playbook: + + - hosts: windows + tasks: + - win_script: foo.ps1 --argument --other-argument + +.. _windows_developers_developers_developers: + +Developers: Supported modules and how it works +`````````````````````````````````````````````` + +Developing ansible modules are covered in a later section, with a focus on Linux/Unix. + +For Windows, ansible modules are implemented in Powershell. Skim the module development chapters before proceeding. + +Windows modules live in a "windows/" subfolder in the Ansible "library/" subtree. For example, if a module is named +"library/windows/win_ping", there will be embedded documentation in the "win_ping" file, and the actual powershell code will +live in a "win_ping.ps1" file. + +Modules (ps1 files) should start as follows:: + + #!powershell + # WANT_JSON + # POWERSHELL_COMMON + + # + # code goes here, reading in stdin as JSON and outputting JSON + +The above magic is neccessary to tell Ansible to mix in some common code and also know how to push modules out. + +Taking a look at the sources for win_ping and the equivalent will make it easier to understand how things work by following +the existing patterns. + +Additional modules may be submitted as pull requests to github. + +.. _windows_system_prep: + +System Prep +``````````` + +In order for Ansible to manage your windows machines you will have to enable powershell remoting first:: + + How to do it goes here + +.. _windows_and_linux_control_machine: + +You Must Have a Linux Control Machine +````````````````````````````````````` + +Note running Ansible from a Windows control machine is NOT a goal of the project. Refrain from asking for this feature, +as it limits what technologies, features, and code we can use in the main project in the future. A Linux control machine +will be required to manage Windows hosts. + +Cygwin is not supported, so please do not ask questions about Ansible running from Cygwin. + +.. seealso:: + + :doc:`developing_modules` + How to write modules + :doc:`playbooks` + Learning ansible's configuration management language + `Mailing List `_ + Questions? Help? Ideas? Stop by the list on Google Groups + `irc.freenode.net `_ + #ansible IRC chat channel + From ac6ccb77cc7f7aca228f3e8a28c9e00df17d70fc Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 17 Jun 2014 13:41:50 -0500 Subject: [PATCH 004/107] Intro windows docs. --- docsite/rst/intro_windows.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 21c28ad1f6..584d66d9e5 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -33,11 +33,8 @@ of the remote hosts:: [windows] - windoze - - [windows:vars] - ansible_connection=winrm - ansible_ssh_port=5986 # Default for HTTPS, HTTP uses 5985 + winserver1.example.com + winserver2.example.com In group_vars/windows.yml, define the following inventory variables:: From 14dab9870b65c1b64282e14729c77638604f8e36 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Tue, 17 Jun 2014 15:44:06 -0500 Subject: [PATCH 005/107] Instantiate psobject directly --- library/windows/assemble.ps1 | 2 +- library/windows/async_wrapper.ps1 | 2 +- library/windows/command.ps1 | 2 +- library/windows/copy.ps1 | 2 +- library/windows/file.ps1 | 2 +- library/windows/ping.ps1 | 2 +- library/windows/slurp.ps1 | 4 ++-- library/windows/stat.ps1 | 6 +++--- library/windows/win_ping | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/library/windows/assemble.ps1 b/library/windows/assemble.ps1 index 77385b5610..dc95d1b9f5 100644 --- a/library/windows/assemble.ps1 +++ b/library/windows/assemble.ps1 @@ -8,6 +8,6 @@ If ($args.Length -gt 0) $data = 'FIXME'; -$result = '{}' | ConvertFrom-Json; +$result = New-Object psobject; $result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; echo $result | ConvertTo-Json; diff --git a/library/windows/async_wrapper.ps1 b/library/windows/async_wrapper.ps1 index 77385b5610..dc95d1b9f5 100644 --- a/library/windows/async_wrapper.ps1 +++ b/library/windows/async_wrapper.ps1 @@ -8,6 +8,6 @@ If ($args.Length -gt 0) $data = 'FIXME'; -$result = '{}' | ConvertFrom-Json; +$result = New-Object psobject; $result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; echo $result | ConvertTo-Json; diff --git a/library/windows/command.ps1 b/library/windows/command.ps1 index 77385b5610..dc95d1b9f5 100644 --- a/library/windows/command.ps1 +++ b/library/windows/command.ps1 @@ -8,6 +8,6 @@ If ($args.Length -gt 0) $data = 'FIXME'; -$result = '{}' | ConvertFrom-Json; +$result = New-Object psobject; $result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; echo $result | ConvertTo-Json; diff --git a/library/windows/copy.ps1 b/library/windows/copy.ps1 index 77385b5610..dc95d1b9f5 100644 --- a/library/windows/copy.ps1 +++ b/library/windows/copy.ps1 @@ -8,6 +8,6 @@ If ($args.Length -gt 0) $data = 'FIXME'; -$result = '{}' | ConvertFrom-Json; +$result = New-Object psobject; $result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; echo $result | ConvertTo-Json; diff --git a/library/windows/file.ps1 b/library/windows/file.ps1 index 77385b5610..dc95d1b9f5 100644 --- a/library/windows/file.ps1 +++ b/library/windows/file.ps1 @@ -8,6 +8,6 @@ If ($args.Length -gt 0) $data = 'FIXME'; -$result = '{}' | ConvertFrom-Json; +$result = New-Object psobject; $result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; echo $result | ConvertTo-Json; diff --git a/library/windows/ping.ps1 b/library/windows/ping.ps1 index 02560873f5..ee46cfd4b8 100644 --- a/library/windows/ping.ps1 +++ b/library/windows/ping.ps1 @@ -12,6 +12,6 @@ If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'data' $data = $params.data; } -$result = '{}' | ConvertFrom-Json; +$result = New-Object psobject; $result | Add-Member -MemberType NoteProperty -Name ping -Value $data; echo $result | ConvertTo-Json; diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 index 93a698491f..21e912f46a 100644 --- a/library/windows/slurp.ps1 +++ b/library/windows/slurp.ps1 @@ -1,7 +1,7 @@ #!powershell # WANT_JSON -$params = '{}' | ConvertFrom-Json; +$params = New-Object psobject; If ($args.Length -gt 0) { $params = Get-Content $args[0] | ConvertFrom-Json; @@ -27,7 +27,7 @@ If (-not $src) $bytes = [System.IO.File]::ReadAllBytes($src); $content = [System.Convert]::ToBase64String($bytes); -$result = '{}' | ConvertFrom-Json; +$result = New-Object psobject; $result | Add-Member -MemberType NoteProperty -Name content -Value $content; $result | Add-Member -MemberType NoteProperty -Name encoding -Value 'base64'; echo $result | ConvertTo-Json; diff --git a/library/windows/stat.ps1 b/library/windows/stat.ps1 index f779b2531e..2e87b12275 100644 --- a/library/windows/stat.ps1 +++ b/library/windows/stat.ps1 @@ -1,7 +1,7 @@ #!powershell # WANT_JSON -$params = '{}' | ConvertFrom-Json; +$params = New-Object psobject; If ($args.Length -gt 0) { $params = Get-Content $args[0] | ConvertFrom-Json; @@ -19,7 +19,7 @@ If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'get_m $get_md5 = $params.get_md5; } -$stat = '{}' | ConvertFrom-Json; +$stat = New-Object psobject; If (Test-Path $path) { $stat | Add-Member -MemberType NoteProperty -Name exists -Value $TRUE; @@ -45,7 +45,7 @@ If ($get_md5 -and $stat.exists -and -not $stat.isdir) $stat | Add-Member -MemberType NoteProperty -Name md5 -Value $path_md5; } -$result = '{}' | ConvertFrom-Json; +$result = New-Object psobject; $result | Add-Member -MemberType NoteProperty -Name stat -Value $stat; $result | Add-Member -MemberType NoteProperty -Name changed -Value $FALSE; echo $result | ConvertTo-Json; diff --git a/library/windows/win_ping b/library/windows/win_ping index f64134454d..8eba171d0e 100644 --- a/library/windows/win_ping +++ b/library/windows/win_ping @@ -12,7 +12,7 @@ If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'data' $data = $params.data; } -$result = '{}' | ConvertFrom-Json; +$result = New-Object psobject; $result | Add-Member -MemberType NoteProperty -Name ping -Value $data; echo $result | ConvertTo-Json; From 6cda35e1d3642138c19838e86c260ceaa4656185 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Tue, 17 Jun 2014 15:44:53 -0500 Subject: [PATCH 006/107] Use more simple check for existence of member --- library/windows/ping.ps1 | 2 +- library/windows/slurp.ps1 | 4 ++-- library/windows/stat.ps1 | 4 ++-- library/windows/win_ping | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/windows/ping.ps1 b/library/windows/ping.ps1 index ee46cfd4b8..8e5fc1f2c9 100644 --- a/library/windows/ping.ps1 +++ b/library/windows/ping.ps1 @@ -7,7 +7,7 @@ If ($args.Length -gt 0) } $data = 'pong'; -If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'data') +If ($params.data.GetType) { $data = $params.data; } diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 index 21e912f46a..6678b9a8d8 100644 --- a/library/windows/slurp.ps1 +++ b/library/windows/slurp.ps1 @@ -8,13 +8,13 @@ If ($args.Length -gt 0) } $src = ''; -If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'src') +If ($params.src.GetType) { $src = $params.src; } Else { - If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'path') + If ($params.path.GetType) { $src = $params.path; } diff --git a/library/windows/stat.ps1 b/library/windows/stat.ps1 index 2e87b12275..14e00d8f55 100644 --- a/library/windows/stat.ps1 +++ b/library/windows/stat.ps1 @@ -8,13 +8,13 @@ If ($args.Length -gt 0) } $path = ''; -If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'path') +If ($params.path.GetType) { $path = $params.path; } $get_md5 = $TRUE; -If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'get_md5') +If ($params.get_md5.GetType) { $get_md5 = $params.get_md5; } diff --git a/library/windows/win_ping b/library/windows/win_ping index 8eba171d0e..c8e3042dee 100644 --- a/library/windows/win_ping +++ b/library/windows/win_ping @@ -7,7 +7,7 @@ If ($args.Length -gt 0) } $data = 'pong'; -If (($params | Get-Member | Select-Object -ExpandProperty Name) -contains 'data') +If ($params.data.GetType) { $data = $params.data; } From 80499346d14b3bd605a14a01614a82119b4f736b Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 17 Jun 2014 15:59:02 -0500 Subject: [PATCH 007/107] Remove stray FIXME --- lib/ansible/runner/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 4bc78bc3b1..e2c3832a87 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -198,7 +198,7 @@ class Runner(object): self.vault_pass = vault_pass self.no_log = no_log - if self.transport == 'smart': # FIXME + if self.transport == 'smart': # if the transport is 'smart' see if SSH can support ControlPersist if not use paramiko # 'smart' is the default since 1.2.1/1.3 cmd = subprocess.Popen(['ssh','-o','ControlPersist'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) From 3ac86e57f4f6233f847129dd1349076a5b9a43a7 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 17 Jun 2014 16:04:50 -0500 Subject: [PATCH 008/107] FIXME comment cleanup --- lib/ansible/runner/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index e2c3832a87..f5761ff37a 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -284,7 +284,7 @@ class Runner(object): # ***************************************************** - def _compute_environment_string(self, conn, inject=None): # CCTODO: Changed this method signature + def _compute_environment_string(self, conn, inject=None): ''' what environment variables to use when running the command? ''' enviro = {} @@ -759,9 +759,7 @@ class Runner(object): if not self.accelerate_port: self.accelerate_port = C.ACCELERATE_PORT - # CCTODO: Any reason not to do this regardless of connection type, and let the connection plugin ignore it? - if True:#actual_transport in [ 'paramiko', 'ssh', 'accelerate', 'winrm' ]: - actual_port = inject.get('ansible_ssh_port', port) + actual_port = inject.get('ansible_ssh_port', port) # the delegated host may have different SSH port configured, etc # and we need to transfer those, and only those, variables From 35a7c93c76b0522a24717625739b69a0f502c971 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 17 Jun 2014 16:11:20 -0500 Subject: [PATCH 009/107] Added comment about implementation line. --- lib/ansible/runner/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index f5761ff37a..9369a274c6 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -942,6 +942,7 @@ class Runner(object): ''' execute a command string over SSH, return the output ''' if not cmd: + # this can happen with powershell modules when there is no analog to a Windows command (like chmod) return dict(stdout='', stderr='') if executable is None: From f7af29680be55488335e10f06764747929cb458f Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 17 Jun 2014 16:12:49 -0500 Subject: [PATCH 010/107] Add default license boilerplate, refactor common powershell code, fixes for raw/script modules. --- lib/ansible/runner/action_plugins/script.py | 1 - lib/ansible/runner/connection.py | 4 - .../runner/connection_plugins/winrm.py | 75 +++------------- lib/ansible/runner/shell_plugins/csh.py | 17 ++++ lib/ansible/runner/shell_plugins/fish.py | 17 ++++ .../runner/shell_plugins/powershell.py | 89 +++++++++++-------- lib/ansible/runner/shell_plugins/sh.py | 19 +++- 7 files changed, 113 insertions(+), 109 deletions(-) diff --git a/lib/ansible/runner/action_plugins/script.py b/lib/ansible/runner/action_plugins/script.py index 2fac86a3f2..e26da444b9 100644 --- a/lib/ansible/runner/action_plugins/script.py +++ b/lib/ansible/runner/action_plugins/script.py @@ -106,7 +106,6 @@ class ActionModule(object): # transfer the file to a remote tmp location source = source.replace('\x00', '') # why does this happen here? args = args.replace('\x00', '') # why does this happen here? - #tmp_src = os.path.join(tmp, os.path.basename(source)) # CCTODO tmp_src = conn.shell.join_path(tmp, os.path.basename(source)) tmp_src = tmp_src.replace('\x00', '') diff --git a/lib/ansible/runner/connection.py b/lib/ansible/runner/connection.py index d7def56fbf..36a0ae0a62 100644 --- a/lib/ansible/runner/connection.py +++ b/lib/ansible/runner/connection.py @@ -20,10 +20,6 @@ from ansible import utils from ansible.errors import AnsibleError -import ansible.constants as C - -import os -import os.path class Connector(object): ''' Handles abstract connections to remote hosts ''' diff --git a/lib/ansible/runner/connection_plugins/winrm.py b/lib/ansible/runner/connection_plugins/winrm.py index 10a9872fa4..5b6a019085 100644 --- a/lib/ansible/runner/connection_plugins/winrm.py +++ b/lib/ansible/runner/connection_plugins/winrm.py @@ -1,6 +1,6 @@ # (c) 2014, Chris Church # -# This file is (not yet) part of Ansible. +# 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 @@ -28,6 +28,7 @@ import urlparse from ansible import errors from ansible import utils from ansible.callbacks import vvv, vvvv +from ansible.runner.shell_plugins import powershell try: from winrm import Response @@ -36,12 +37,6 @@ try: except ImportError: raise errors.AnsibleError("winrm is not installed") -# When running with unmodified Ansible (1.6.x), load local hacks. -try: - _winrm_hacks = imp.load_source('_winrm_hacks', os.path.join(os.path.dirname(__file__), '_winrm_hacks.py')) -except (ImportError, IOError): - _winrm_hacks = None - _winrm_cache = { # 'user:pwhash@host:port': } @@ -61,17 +56,12 @@ class Connection(object): self.protocol = None self.shell_id = None self.delegate = None - if _winrm_hacks: - _winrm_hacks.patch_module_finder(self) def _winrm_connect(self): ''' Establish a WinRM connection over HTTP/HTTPS. ''' - if _winrm_hacks: - port = _winrm_hacks.get_port(self) - else: - port = self.port or 5986 + port = self.port or 5986 vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \ (self.user, port, self.host), host=self.host) netloc = '%s:%d' % (self.host, port) @@ -105,35 +95,9 @@ class Connection(object): return protocol vvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self.host) continue - # FIXME: Cache connection!!! if exc: raise exc - def _winrm_escape(self, value, include_vars=False): - ''' - Return value escaped for use in PowerShell command. - ''' - # http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences - # http://stackoverflow.com/questions/764360/a-list-of-string-replacements-in-python - subs = [('\n', '`n'), ('\r', '`r'), ('\t', '`t'), ('\a', '`a'), - ('\b', '`b'), ('\f', '`f'), ('\v', '`v'), ('"', '`"'), - ('\'', '`\''), ('`', '``'), ('\x00', '`0')] - if include_vars: - subs.append(('$', '`$')) - pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs) - substs = [s for p, s in subs] - replace = lambda m: substs[m.lastindex - 1] - return re.sub(pattern, replace, value) - - def _winrm_get_script_cmd(self, script): - ''' - Convert a PowerShell script to a single base64-encoded command. - ''' - vvvv('WINRM SCRIPT: %s' % script, host=self.host) - encoded_script = base64.b64encode(script.encode('utf-16-le')) - return ['PowerShell', '-NoProfile', '-NonInteractive', - '-EncodedCommand', encoded_script] - def _winrm_exec(self, command, args): vvvv("WINRM EXEC %r %r" % (command, args), host=self.host) if not self.protocol: @@ -152,27 +116,19 @@ class Connection(object): self.protocol.cleanup_command(self.shell_id, command_id) def connect(self): - if not _winrm_hacks: - if not self.protocol: - self.protocol = self._winrm_connect() - # When using hacks, connect lazily on first command, to allow for - # runner to set self.delegate, needed if actual host vs. host name are - # different. + if not self.protocol: + self.protocol = self._winrm_connect() return self - def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None): + def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable=None, in_data=None, su=None, su_user=None): cmd = cmd.encode('utf-8') vvv("EXEC %s" % cmd, host=self.host) cmd_parts = shlex.split(cmd, posix=False) vvvv("WINRM PARTS %r" % cmd_parts, host=self.host) # For script/raw support. - if len(cmd_parts) == 1 and cmd_parts[0].lower().endswith('.ps1'): - cmd_parts = ['PowerShell', '-ExecutionPolicy', 'Unrestricted', '-File', cmd_parts[0]] - if _winrm_hacks: - cmd_parts = _winrm_hacks.filter_cmd_parts(self, cmd_parts) - if not cmd_parts: - vvv('WINRM NOOP') - return (0, '', '', '') + if cmd_parts and cmd_parts[0].lower().endswith('.ps1'): + script = 'PowerShell -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File ' + ' '.join(['"%s"' % x for x in cmd_parts]) + cmd_parts = powershell._encode_script(script, as_list=True) try: result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) except Exception, e: @@ -181,8 +137,6 @@ class Connection(object): return (result.status_code, '', result.std_out.encode('utf-8'), result.std_err.encode('utf-8')) def put_file(self, in_path, out_path): - if _winrm_hacks: - out_path = _winrm_hacks.fix_slashes(out_path) vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) if not os.path.exists(in_path): raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) @@ -205,19 +159,16 @@ class Connection(object): $stream.Write($buffer, 0, $buffer.length) | Out-Null; $stream.SetLength(%d) | Out-Null; $stream.Close() | Out-Null; - ''' % (buffer_size, self._winrm_escape(out_path), offset, b64_data, in_size) - cmd_parts = self._winrm_get_script_cmd(script) + ''' % (buffer_size, powershell._escape(out_path), offset, b64_data, in_size) + cmd_parts = powershell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) if result.status_code != 0: raise RuntimeError(result.std_err.encode('utf-8')) - script = u'' except Exception: # IOError? traceback.print_exc() raise errors.AnsibleError("failed to transfer file to %s" % out_path) def fetch_file(self, in_path, out_path): - if _winrm_hacks: - in_path = _winrm_hacks.fix_slashes(in_path) out_path = out_path.replace('\\', '/') vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host) buffer_size = 2**20 # 1MB chunks @@ -236,8 +187,8 @@ class Connection(object): $bytes = $buffer[0..($bytesRead-1)]; [System.Convert]::ToBase64String($bytes); $stream.Close() | Out-Null; - ''' % (buffer_size, self._winrm_escape(in_path), offset) - cmd_parts = self._winrm_get_script_cmd(script) + ''' % (buffer_size, powershell._escape(in_path), offset) + cmd_parts = powershell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) data = base64.b64decode(result.std_out.strip()) out_file.write(data) diff --git a/lib/ansible/runner/shell_plugins/csh.py b/lib/ansible/runner/shell_plugins/csh.py index e52ad55528..137c013c12 100644 --- a/lib/ansible/runner/shell_plugins/csh.py +++ b/lib/ansible/runner/shell_plugins/csh.py @@ -1,3 +1,20 @@ +# (c) 2014, Chris Church +# +# 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 . + from ansible.runner.shell_plugins.sh import ShellModule as ShModule class ShellModule(ShModule): diff --git a/lib/ansible/runner/shell_plugins/fish.py b/lib/ansible/runner/shell_plugins/fish.py index e52ad55528..137c013c12 100644 --- a/lib/ansible/runner/shell_plugins/fish.py +++ b/lib/ansible/runner/shell_plugins/fish.py @@ -1,3 +1,20 @@ +# (c) 2014, Chris Church +# +# 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 . + from ansible.runner.shell_plugins.sh import ShellModule as ShModule class ShellModule(ShModule): diff --git a/lib/ansible/runner/shell_plugins/powershell.py b/lib/ansible/runner/shell_plugins/powershell.py index 185116a406..75f1a20478 100644 --- a/lib/ansible/runner/shell_plugins/powershell.py +++ b/lib/ansible/runner/shell_plugins/powershell.py @@ -1,3 +1,20 @@ +# (c) 2014, Chris Church +# +# 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 . + import base64 import os import re @@ -5,35 +22,30 @@ import random import shlex import time +def _escape(value, include_vars=False): + '''Return value escaped for use in PowerShell command.''' + # http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences + # http://stackoverflow.com/questions/764360/a-list-of-string-replacements-in-python + subs = [('\n', '`n'), ('\r', '`r'), ('\t', '`t'), ('\a', '`a'), + ('\b', '`b'), ('\f', '`f'), ('\v', '`v'), ('"', '`"'), + ('\'', '`\''), ('`', '``'), ('\x00', '`0')] + if include_vars: + subs.append(('$', '`$')) + pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs) + substs = [s for p, s in subs] + replace = lambda m: substs[m.lastindex - 1] + return re.sub(pattern, replace, value) + +def _encode_script(script, as_list=False): + '''Convert a PowerShell script to a single base64-encoded command.''' + encoded_script = base64.b64encode(script.encode('utf-16-le')) + cmd_parts = ['PowerShell', '-NoProfile', '-NonInteractive', '-EncodedCommand', encoded_script] + if as_list: + return cmd_parts + return ' '.join(cmd_parts) + class ShellModule(object): - def __init__(self): - pass - - def _escape(self, value, include_vars=False): - ''' - Return value escaped for use in PowerShell command. - ''' - # http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences - # http://stackoverflow.com/questions/764360/a-list-of-string-replacements-in-python - subs = [('\n', '`n'), ('\r', '`r'), ('\t', '`t'), ('\a', '`a'), - ('\b', '`b'), ('\f', '`f'), ('\v', '`v'), ('"', '`"'), - ('\'', '`\''), ('`', '``'), ('\x00', '`0')] - if include_vars: - subs.append(('$', '`$')) - pattern = '|'.join('(%s)' % re.escape(p) for p, s in subs) - substs = [s for p, s in subs] - replace = lambda m: substs[m.lastindex - 1] - return re.sub(pattern, replace, value) - - def _get_script_cmd(self, script): - ''' - Convert a PowerShell script to a single base64-encoded command. - ''' - encoded_script = base64.b64encode(script.encode('utf-16-le')) - return ' '.join(['PowerShell', '-NoProfile', '-NonInteractive', - '-EncodedCommand', encoded_script]) - def env_prefix(self, **kwargs): return '' @@ -44,22 +56,20 @@ class ShellModule(object): return '' def remove(self, path, recurse=False): - path = self._escape(path) + path = _escape(path) if recurse: - return self._get_script_cmd('''Remove-Item "%s" -Force -Recurse;''' % path) + return _encode_script('''Remove-Item "%s" -Force -Recurse;''' % path) else: - return self._get_script_cmd('''Remove-Item "%s" -Force;''' % path) + return _encode_script('''Remove-Item "%s" -Force;''' % path) - def mkdtemp(self, basefile=None, system=False, mode=None): - if not basefile: - basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) - basefile = self._escape(basefile) + def mkdtemp(self, basefile, system=False, mode=None): + basefile = _escape(basefile) # FIXME: Support system temp path! - return self._get_script_cmd('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName;''' % basefile) + return _encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName;''' % basefile) def md5(self, path): - path = self._escape(path) - return self._get_script_cmd('''(Get-FileHash -Path "%s" -Algorithm MD5).Hash.ToLower();''' % path) + path = _escape(path) + return _encode_script('''(Get-FileHash -Path "%s" -Algorithm MD5).Hash.ToLower();''' % path) def build_module_command(self, env_string, shebang, cmd, rm_tmp=None): cmd_parts = shlex.split(cmd, posix=False) @@ -68,5 +78,6 @@ class ShellModule(object): cmd_parts = ['PowerShell', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Unrestricted', '-File'] + ['"%s"' % x for x in cmd_parts] script = ' '.join(cmd_parts) if rm_tmp: - script = '%s; Remove-Item "%s" -Force -Recurse;' % (script, self._escape(rm_tmp)) - return self._get_script_cmd(script) + rm_tmp = _escape(rm_tmp) + script = '%s; Remove-Item "%s" -Force -Recurse;' % (script, rm_tmp) + return _encode_script(script) diff --git a/lib/ansible/runner/shell_plugins/sh.py b/lib/ansible/runner/shell_plugins/sh.py index 9634f97205..8b74386d85 100644 --- a/lib/ansible/runner/shell_plugins/sh.py +++ b/lib/ansible/runner/shell_plugins/sh.py @@ -1,3 +1,19 @@ +# (c) 2014, Chris Church +# +# 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 . import os import pipes @@ -5,9 +21,6 @@ import ansible.constants as C class ShellModule(object): - def __init__(self): - pass - def env_prefix(self, **kwargs): '''Build command prefix with environment variables.''' env = dict( From bafa63b4245b0ac86fffc30de0ae8bdfa25a4d79 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 17 Jun 2014 16:14:22 -0500 Subject: [PATCH 011/107] Revise documentation on powershell module replacer code. --- lib/ansible/module_common.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/ansible/module_common.py b/lib/ansible/module_common.py index 4bcaaff27b..fc74e91f65 100644 --- a/lib/ansible/module_common.py +++ b/lib/ansible/module_common.py @@ -47,17 +47,16 @@ class ModuleReplacer(object): from ansible.module_utils.basic import * - will result in a template evaluation of - - {{ include 'basic.py' }} + ... will result in the insertion basic.py into the module from the module_utils/ directory in the source tree. All modules are required to import at least basic, though there will also be other snippets. - # POWERSHELL_COMMON will also map to - {{ include 'powershell.ps1' }} + # POWERSHELL_COMMON + + Also results in the inclusion of the common code in powershell.ps1 """ From a25c441300d5a82d57c3410620f4200f860fdd3f Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Tue, 17 Jun 2014 18:20:33 -0500 Subject: [PATCH 012/107] Add shared functions to module_utils/powershell.ps1 and refactor powershell modules to utilize the common powershell code --- lib/ansible/module_utils/powershell.ps1 | 19 ++++++ library/windows/assemble.ps1 | 8 +-- library/windows/async_wrapper.ps1 | 8 +-- library/windows/command.ps1 | 8 +-- library/windows/copy.ps1 | 8 +-- library/windows/file.ps1 | 8 +-- library/windows/ping.ps1 | 8 +-- library/windows/slurp.ps1 | 11 ++-- library/windows/stat.ps1 | 23 +++---- library/windows/win_ping | 87 ------------------------- library/windows/win_ping.ps1 | 15 +++++ 11 files changed, 66 insertions(+), 137 deletions(-) delete mode 100644 library/windows/win_ping create mode 100644 library/windows/win_ping.ps1 diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index 34f80fcd47..54d2eedc4f 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -27,4 +27,23 @@ # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +# Helper function to parse Ansible JSON arguments from a file passed as +# the single argument to the module +Function Parse-Args($arguments) +{ + $parameters = New-Object psobject; + If ($arguments.Length -gt 0) + { + $parameters = Get-Content $arguments[0] | ConvertFrom-Json; + } + $parameters; +} + +# Helper function to set an "attribute" on a psobject instance in powershell. +# This is a convenience to make adding Members to the object easier and +# slightly more pythonic +Function Set-Attr($obj, $name, $value) +{ + $obj | Add-Member -Force -MemberType NoteProperty -Name $name -Value $value +} diff --git a/library/windows/assemble.ps1 b/library/windows/assemble.ps1 index dc95d1b9f5..90266a9e6b 100644 --- a/library/windows/assemble.ps1 +++ b/library/windows/assemble.ps1 @@ -1,13 +1,11 @@ #!powershell # WANT_JSON +# POWERSHELL_COMMON -If ($args.Length -gt 0) -{ - $params = Get-Content $args[0] | ConvertFrom-Json; -} +$params = Parse-Args $args; $data = 'FIXME'; $result = New-Object psobject; -$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +Set-Attr $result "fixme" $data; echo $result | ConvertTo-Json; diff --git a/library/windows/async_wrapper.ps1 b/library/windows/async_wrapper.ps1 index dc95d1b9f5..90266a9e6b 100644 --- a/library/windows/async_wrapper.ps1 +++ b/library/windows/async_wrapper.ps1 @@ -1,13 +1,11 @@ #!powershell # WANT_JSON +# POWERSHELL_COMMON -If ($args.Length -gt 0) -{ - $params = Get-Content $args[0] | ConvertFrom-Json; -} +$params = Parse-Args $args; $data = 'FIXME'; $result = New-Object psobject; -$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +Set-Attr $result "fixme" $data; echo $result | ConvertTo-Json; diff --git a/library/windows/command.ps1 b/library/windows/command.ps1 index dc95d1b9f5..90266a9e6b 100644 --- a/library/windows/command.ps1 +++ b/library/windows/command.ps1 @@ -1,13 +1,11 @@ #!powershell # WANT_JSON +# POWERSHELL_COMMON -If ($args.Length -gt 0) -{ - $params = Get-Content $args[0] | ConvertFrom-Json; -} +$params = Parse-Args $args; $data = 'FIXME'; $result = New-Object psobject; -$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +Set-Attr $result "fixme" $data; echo $result | ConvertTo-Json; diff --git a/library/windows/copy.ps1 b/library/windows/copy.ps1 index dc95d1b9f5..90266a9e6b 100644 --- a/library/windows/copy.ps1 +++ b/library/windows/copy.ps1 @@ -1,13 +1,11 @@ #!powershell # WANT_JSON +# POWERSHELL_COMMON -If ($args.Length -gt 0) -{ - $params = Get-Content $args[0] | ConvertFrom-Json; -} +$params = Parse-Args $args; $data = 'FIXME'; $result = New-Object psobject; -$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +Set-Attr $result "fixme" $data; echo $result | ConvertTo-Json; diff --git a/library/windows/file.ps1 b/library/windows/file.ps1 index dc95d1b9f5..90266a9e6b 100644 --- a/library/windows/file.ps1 +++ b/library/windows/file.ps1 @@ -1,13 +1,11 @@ #!powershell # WANT_JSON +# POWERSHELL_COMMON -If ($args.Length -gt 0) -{ - $params = Get-Content $args[0] | ConvertFrom-Json; -} +$params = Parse-Args $args; $data = 'FIXME'; $result = New-Object psobject; -$result | Add-Member -MemberType NoteProperty -Name fixme -Value $data; +Set-Attr $result "fixme" $data; echo $result | ConvertTo-Json; diff --git a/library/windows/ping.ps1 b/library/windows/ping.ps1 index 8e5fc1f2c9..5550092a41 100644 --- a/library/windows/ping.ps1 +++ b/library/windows/ping.ps1 @@ -1,10 +1,8 @@ #!powershell # WANT_JSON +# POWERSHELL_COMMON -If ($args.Length -gt 0) -{ - $params = Get-Content $args[0] | ConvertFrom-Json; -} +$params = Parse-Args $args; $data = 'pong'; If ($params.data.GetType) @@ -13,5 +11,5 @@ If ($params.data.GetType) } $result = New-Object psobject; -$result | Add-Member -MemberType NoteProperty -Name ping -Value $data; +Set-Attr $result "ping" $data; echo $result | ConvertTo-Json; diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 index 6678b9a8d8..041e678d52 100644 --- a/library/windows/slurp.ps1 +++ b/library/windows/slurp.ps1 @@ -1,11 +1,8 @@ #!powershell # WANT_JSON +# POWERSHELL_COMMON -$params = New-Object psobject; -If ($args.Length -gt 0) -{ - $params = Get-Content $args[0] | ConvertFrom-Json; -} +$params = Parse-Args $args; $src = ''; If ($params.src.GetType) @@ -28,6 +25,6 @@ $bytes = [System.IO.File]::ReadAllBytes($src); $content = [System.Convert]::ToBase64String($bytes); $result = New-Object psobject; -$result | Add-Member -MemberType NoteProperty -Name content -Value $content; -$result | Add-Member -MemberType NoteProperty -Name encoding -Value 'base64'; +Set-Attr $result "content" $content; +Set-Attr $result "encoding" "base64"; echo $result | ConvertTo-Json; diff --git a/library/windows/stat.ps1 b/library/windows/stat.ps1 index 14e00d8f55..b7654a1010 100644 --- a/library/windows/stat.ps1 +++ b/library/windows/stat.ps1 @@ -1,11 +1,8 @@ #!powershell # WANT_JSON +# POWERSHELL_COMMON -$params = New-Object psobject; -If ($args.Length -gt 0) -{ - $params = Get-Content $args[0] | ConvertFrom-Json; -} +$params = Parse-Args $args; $path = ''; If ($params.path.GetType) @@ -22,30 +19,30 @@ If ($params.get_md5.GetType) $stat = New-Object psobject; If (Test-Path $path) { - $stat | Add-Member -MemberType NoteProperty -Name exists -Value $TRUE; + Set-Attr $stat "exists" $TRUE; $info = Get-Item $path; If ($info.Directory) # Only files have the .Directory attribute. { - $stat | Add-Member -MemberType NoteProperty -Name isdir -Value $FALSE; - $stat | Add-Member -MemberType NoteProperty -Name size -Value $info.Length; + Set-Attr $stat "isdir" $FALSE; + Set-Attr $stat "size" $info.Length; } Else { - $stat | Add-Member -MemberType NoteProperty -Name isdir -Value $TRUE; + Set-Attr $stat "isdir" $TRUE; } } Else { - $stat | Add-Member -MemberType NoteProperty -Name exists -Value $FALSE; + Set-Attr $stat "exists" $FALSE; } If ($get_md5 -and $stat.exists -and -not $stat.isdir) { $path_md5 = (Get-FileHash -Path $path -Algorithm MD5).Hash.ToLower(); - $stat | Add-Member -MemberType NoteProperty -Name md5 -Value $path_md5; + Set-Attr $stat "md5" $path_md5; } $result = New-Object psobject; -$result | Add-Member -MemberType NoteProperty -Name stat -Value $stat; -$result | Add-Member -MemberType NoteProperty -Name changed -Value $FALSE; +Set-Attr $result "stat" $stat; +Set-Attr $result "changed" $FALSE; echo $result | ConvertTo-Json; diff --git a/library/windows/win_ping b/library/windows/win_ping deleted file mode 100644 index c8e3042dee..0000000000 --- a/library/windows/win_ping +++ /dev/null @@ -1,87 +0,0 @@ -#!powershell -# WANT_JSON - -If ($args.Length -gt 0) -{ - $params = Get-Content $args[0] | ConvertFrom-Json; -} - -$data = 'pong'; -If ($params.data.GetType) -{ - $data = $params.data; -} - -$result = New-Object psobject; -$result | Add-Member -MemberType NoteProperty -Name ping -Value $data; -echo $result | ConvertTo-Json; - -# _______ _ _ -# |__ __| | (_) -# | | | |__ _ ___ -# | | | '_ \| / __| -# | | | | | | \__ \ -# __|_| |_| |_|_|___/ -# |_ _| -# | | ___ -# | | / __| -# _| |_\__ \ -# |___/\|___/ -# / \ -# / /\ \ -# / ____ \ -# /_/ \_\ -# | | -# | | __ _ _ __ __ _ ___ -# | | / _` | '__/ _` |/ _ \ -# | |___| (_| | | | (_| | __/ -# |______\__,_|_| \__, |\___| -# __/ | -# ____ _ |___/ -# | _ \| | | | -# | |_) | | ___ ___| | __ -# | _ <| |/ _ \ / __| |/ / -# | |_) | | (_) | (__| < -# |____/|_|\___/ \___|_|\_\ -# / __ \ / _| -# | | | | |_ -# | | | | _| -# | |__| | | -# \____/|_| __ __ -# / ____| | / _|/ _| -# | (___ | |_ _ _| |_| |_ -# \___ \| __| | | | _| _| -# ____) | |_| |_| | | | | -# |_____/ \__|\__,_|_| |_| -# | | | | -# | |_ _ ___| |_ -# _ | | | | / __| __| -# | |__| | |_| \__ \ |_ -# \____/_\__,_|___/\__| -# |__ __| -# | | ___ -# | |/ _ \ -# | | (_) | -# __|_|\___/ _ -# | \/ | | | -# | \ / | __ _| | _____ -# | |\/| |/ _` | |/ / _ \ -# | | | | (_| | < __/ -# |_|__|_|\__,_|_|\_\___| -# |__ __| | -# | | | |__ ___ -# | | | '_ \ / _ \ -# | | | | | | __/ -# __|_|_ |_| |_|\___| -# | ____(_) | -# | |__ _| | ___ -# | __| | | |/ _ \ -# | | | | | __/ -# |_|__ |_|_|\___| -# | _ \(_) -# | |_) |_ __ _ __ _ ___ _ __ -# | _ <| |/ _` |/ _` |/ _ \ '__| -# | |_) | | (_| | (_| | __/ | -# |____/|_|\__, |\__, |\___|_| -# __/ | __/ | -# |___/ |___/ diff --git a/library/windows/win_ping.ps1 b/library/windows/win_ping.ps1 new file mode 100644 index 0000000000..5550092a41 --- /dev/null +++ b/library/windows/win_ping.ps1 @@ -0,0 +1,15 @@ +#!powershell +# WANT_JSON +# POWERSHELL_COMMON + +$params = Parse-Args $args; + +$data = 'pong'; +If ($params.data.GetType) +{ + $data = $params.data; +} + +$result = New-Object psobject; +Set-Attr $result "ping" $data; +echo $result | ConvertTo-Json; From e7e95721b930f256658dd69839dbb80e7a109b59 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Tue, 17 Jun 2014 18:29:49 -0500 Subject: [PATCH 013/107] powershell modules will have a .ps1 extension --- lib/ansible/utils/plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/utils/plugins.py b/lib/ansible/utils/plugins.py index 3e2d15ded8..546fc81aec 100644 --- a/lib/ansible/utils/plugins.py +++ b/lib/ansible/utils/plugins.py @@ -146,7 +146,7 @@ class PluginLoader(object): if self.class_name: suffixes = ['.py'] else: - suffixes = [''] + suffixes = ['', '.ps1'] for suffix in suffixes: full_name = '%s%s' % (name, suffix) From fa0943a9b3e84fcc818f5583530b04a362b6963e Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Tue, 17 Jun 2014 18:38:44 -0500 Subject: [PATCH 014/107] Add license header to powersell modules --- library/windows/assemble.ps1 | 15 +++++++++++++++ library/windows/async_wrapper.ps1 | 15 +++++++++++++++ library/windows/command.ps1 | 15 +++++++++++++++ library/windows/copy.ps1 | 15 +++++++++++++++ library/windows/file.ps1 | 15 +++++++++++++++ library/windows/ping.ps1 | 15 +++++++++++++++ library/windows/slurp.ps1 | 15 +++++++++++++++ library/windows/stat.ps1 | 15 +++++++++++++++ library/windows/win_ping.ps1 | 15 +++++++++++++++ 9 files changed, 135 insertions(+) diff --git a/library/windows/assemble.ps1 b/library/windows/assemble.ps1 index 90266a9e6b..e5c4a01907 100644 --- a/library/windows/assemble.ps1 +++ b/library/windows/assemble.ps1 @@ -2,6 +2,21 @@ # WANT_JSON # POWERSHELL_COMMON +# 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 . + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/async_wrapper.ps1 b/library/windows/async_wrapper.ps1 index 90266a9e6b..e5c4a01907 100644 --- a/library/windows/async_wrapper.ps1 +++ b/library/windows/async_wrapper.ps1 @@ -2,6 +2,21 @@ # WANT_JSON # POWERSHELL_COMMON +# 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 . + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/command.ps1 b/library/windows/command.ps1 index 90266a9e6b..e5c4a01907 100644 --- a/library/windows/command.ps1 +++ b/library/windows/command.ps1 @@ -2,6 +2,21 @@ # WANT_JSON # POWERSHELL_COMMON +# 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 . + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/copy.ps1 b/library/windows/copy.ps1 index 90266a9e6b..e5c4a01907 100644 --- a/library/windows/copy.ps1 +++ b/library/windows/copy.ps1 @@ -2,6 +2,21 @@ # WANT_JSON # POWERSHELL_COMMON +# 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 . + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/file.ps1 b/library/windows/file.ps1 index 90266a9e6b..e5c4a01907 100644 --- a/library/windows/file.ps1 +++ b/library/windows/file.ps1 @@ -2,6 +2,21 @@ # WANT_JSON # POWERSHELL_COMMON +# 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 . + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/ping.ps1 b/library/windows/ping.ps1 index 5550092a41..74311119c5 100644 --- a/library/windows/ping.ps1 +++ b/library/windows/ping.ps1 @@ -2,6 +2,21 @@ # WANT_JSON # POWERSHELL_COMMON +# 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 . + $params = Parse-Args $args; $data = 'pong'; diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 index 041e678d52..6f001a4924 100644 --- a/library/windows/slurp.ps1 +++ b/library/windows/slurp.ps1 @@ -2,6 +2,21 @@ # WANT_JSON # POWERSHELL_COMMON +# 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 . + $params = Parse-Args $args; $src = ''; diff --git a/library/windows/stat.ps1 b/library/windows/stat.ps1 index b7654a1010..bf422858ab 100644 --- a/library/windows/stat.ps1 +++ b/library/windows/stat.ps1 @@ -2,6 +2,21 @@ # WANT_JSON # POWERSHELL_COMMON +# 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 . + $params = Parse-Args $args; $path = ''; diff --git a/library/windows/win_ping.ps1 b/library/windows/win_ping.ps1 index 5550092a41..74311119c5 100644 --- a/library/windows/win_ping.ps1 +++ b/library/windows/win_ping.ps1 @@ -2,6 +2,21 @@ # WANT_JSON # POWERSHELL_COMMON +# 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 . + $params = Parse-Args $args; $data = 'pong'; From 0c938562a72f4fa43e85f9ca69234104fcd9da49 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 17 Jun 2014 23:04:17 -0500 Subject: [PATCH 015/107] Add winrm integration tests for raw, script and ping modules. --- test/integration/Makefile | 3 + test/integration/README.md | 20 ++++++ test/integration/inventory.winrm.template | 7 ++ .../roles/test_win_ping/tasks/main.yml | 66 +++++++++++++++++++ .../roles/test_win_raw/tasks/main.yml | 56 ++++++++++++++++ .../test_win_script/files/test_script.bat | 2 + .../test_win_script/files/test_script.ps1 | 2 + .../files/test_script_with_args.ps1 | 7 ++ .../files/test_script_with_errors.ps1 | 11 ++++ .../roles/test_win_script/tasks/main.yml | 59 +++++++++++++++++ test/integration/test_winrm.yml | 8 +++ 11 files changed, 241 insertions(+) create mode 100644 test/integration/inventory.winrm.template create mode 100644 test/integration/roles/test_win_ping/tasks/main.yml create mode 100644 test/integration/roles/test_win_raw/tasks/main.yml create mode 100644 test/integration/roles/test_win_script/files/test_script.bat create mode 100644 test/integration/roles/test_win_script/files/test_script.ps1 create mode 100644 test/integration/roles/test_win_script/files/test_script_with_args.ps1 create mode 100644 test/integration/roles/test_win_script/files/test_script_with_errors.ps1 create mode 100644 test/integration/roles/test_win_script/tasks/main.yml create mode 100644 test/integration/test_winrm.yml diff --git a/test/integration/Makefile b/test/integration/Makefile index 758c150f7c..aefb27dbb8 100644 --- a/test/integration/Makefile +++ b/test/integration/Makefile @@ -46,6 +46,9 @@ test_vault: ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) --syntax-check ansible-playbook test_vault.yml -i $(INVENTORY) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) --vault-password-file $(VAULT_PASSWORD_FILE) +test_winrm: + ansible-playbook test_winrm.yml -i inventory.winrm -e @$(VARS_FILE) $(CREDENTIALS_ARG) -v $(TEST_FLAGS) + cloud: amazon rackspace cloud_cleanup: amazon_cleanup rackspace_cleanup diff --git a/test/integration/README.md b/test/integration/README.md index e05f843ac2..f30697b497 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -70,3 +70,23 @@ resources. Running these tests may result in additional fees associated with your cloud account. Care is taken to ensure that created resources are removed. However, it is advisable to inspect your AWS console to ensure no unexpected resources are running. + +Windows Tests +============= + +These tests exercise the winrm connection plugin and Windows modules. You'll +need to define an inventory with a remote Windows 2008 or 2012 Server to use +for testing, and enable PowerShell Remoting to continue. + +Running these tests may result in changes to your Windows host, so don't run +them against a production/critical Windows environment. + +Enable PowerShell Remoting (run on the Windows host via Remote Desktop): + Enable-PSRemoting -Force + +Define Windows inventory: + cp inventory.winrm.template inventory.winrm + ${EDITOR:-vi} inventory.winrm + +Run the tests: + make test_winrm diff --git a/test/integration/inventory.winrm.template b/test/integration/inventory.winrm.template new file mode 100644 index 0000000000..088e3cc144 --- /dev/null +++ b/test/integration/inventory.winrm.template @@ -0,0 +1,7 @@ +[windows] +server ansible_ssh_host=10.10.10.10 ansible_ssh_user=Administrator ansible_ssh_pass=ShhhDontTellAnyone + +[windows:vars] +ansible_connection=winrm +# HTTPS uses 5986, HTTP uses 5985 +ansible_ssh_port=5985 diff --git a/test/integration/roles/test_win_ping/tasks/main.yml b/test/integration/roles/test_win_ping/tasks/main.yml new file mode 100644 index 0000000000..f3b5fcc4f4 --- /dev/null +++ b/test/integration/roles/test_win_ping/tasks/main.yml @@ -0,0 +1,66 @@ +--- + +- name: test win_ping + action: win_ping + register: win_ping_result + +- name: check win_ping result + assert: + that: + - "not win_ping_result|failed" + - "not win_ping_result|changed" + - "win_ping_result.ping == 'pong'" + +- name: test win_ping with data + win_ping: data=blah + register: win_ping_with_data_result + +- name: check win_ping result with data + assert: + that: + - "not win_ping_with_data_result|failed" + - "not win_ping_with_data_result|changed" + - "win_ping_with_data_result.ping == 'blah'" + +- name: test ping.ps1 with data + ping.ps1: data=bleep + register: ping_ps1_result + +- name: check ping.ps1 result + assert: + that: + - "not ping_ps1_result|failed" + - "not ping_ps1_result|changed" + - "ping_ps1_result.ping == 'bleep'" + +#- name: test ping.ps1 with invalid args +# ping.ps1: arg=invalid +# register: ping_ps1_invalid_args_result + +#- name: check that ping.ps1 with invalid args fails +# assert: +# that: +# - "ping_ps1_invalid_args_result|failed" +# - "ping_ps1_invalid_args_result.msg" + +- name: test local ping (should use default ping) + local_action: ping + register: local_ping_result + +- name: check local ping result + assert: + that: + - "not local_ping_result|failed" + - "not local_ping_result|changed" + - "local_ping_result.ping == 'pong'" + +- name: test ping (should use ping.ps1) + action: ping + register: ping_result + +- name: check ping result + assert: + that: + - "not ping_result|failed" + - "not ping_result|changed" + - "ping_result.ping == 'pong'" diff --git a/test/integration/roles/test_win_raw/tasks/main.yml b/test/integration/roles/test_win_raw/tasks/main.yml new file mode 100644 index 0000000000..a59ea3b624 --- /dev/null +++ b/test/integration/roles/test_win_raw/tasks/main.yml @@ -0,0 +1,56 @@ +--- + +- name: run getmac + raw: getmac + register: getmac_result + +- name: assert that getmac ran + assert: + that: + - "getmac_result.rc == 0" + - "getmac_result.stdout" + - "not getmac_result.stderr" + - "not getmac_result|failed" + - "not getmac_result|changed" + +- name: run ipconfig with /all argument + raw: ipconfig /all + register: ipconfig_result + +- name: assert that ipconfig ran with /all argument + assert: + that: + - "ipconfig_result.rc == 0" + - "ipconfig_result.stdout" + - "'Physical Address' in ipconfig_result.stdout" + - "not ipconfig_result.stderr" + - "not ipconfig_result|failed" + - "not ipconfig_result|changed" + +- name: run ipconfig with invalid argument + raw: ipconfig /badswitch + register: ipconfig_invalid_result + ignore_errors: true + +- name: assert that ipconfig with invalid argument failed + assert: + that: + - "ipconfig_invalid_result.rc != 0" + - "ipconfig_invalid_result.stdout" # ipconfig displays errors on stdout. + - "not ipconfig_invalid_result.stderr" + - "ipconfig_invalid_result|failed" + - "not ipconfig_invalid_result|changed" + +- name: run an unknown command + raw: uname -a + register: unknown_result + ignore_errors: true + +- name: assert that an unknown command failed + assert: + that: + - "unknown_result.rc != 0" + - "not unknown_result.stdout" + - "unknown_result.stderr" # An unknown command displays error on stderr. + - "unknown_result|failed" + - "not unknown_result|changed" diff --git a/test/integration/roles/test_win_script/files/test_script.bat b/test/integration/roles/test_win_script/files/test_script.bat new file mode 100644 index 0000000000..05cc2d19ec --- /dev/null +++ b/test/integration/roles/test_win_script/files/test_script.bat @@ -0,0 +1,2 @@ +@ECHO OFF +ECHO We can even run a batch file! diff --git a/test/integration/roles/test_win_script/files/test_script.ps1 b/test/integration/roles/test_win_script/files/test_script.ps1 new file mode 100644 index 0000000000..9978f36341 --- /dev/null +++ b/test/integration/roles/test_win_script/files/test_script.ps1 @@ -0,0 +1,2 @@ +# Test script to make sure the Ansible script module works. +Write-Host "Woohoo! We can run a PowerShell script via Ansible!" diff --git a/test/integration/roles/test_win_script/files/test_script_with_args.ps1 b/test/integration/roles/test_win_script/files/test_script_with_args.ps1 new file mode 100644 index 0000000000..520aafa395 --- /dev/null +++ b/test/integration/roles/test_win_script/files/test_script_with_args.ps1 @@ -0,0 +1,7 @@ +# Test script to make sure the Ansible script module works when arguments are +# passed to the script. + +foreach ($i in $args) +{ + Write-Host $i; +} diff --git a/test/integration/roles/test_win_script/files/test_script_with_errors.ps1 b/test/integration/roles/test_win_script/files/test_script_with_errors.ps1 new file mode 100644 index 0000000000..ad80f68f8b --- /dev/null +++ b/test/integration/roles/test_win_script/files/test_script_with_errors.ps1 @@ -0,0 +1,11 @@ +# http://stackoverflow.com/questions/9948517/how-to-stop-a-powershell-script-on-the-first-error +#$ErrorActionPreference = "Stop"; +# http://stackoverflow.com/questions/15777492/why-are-my-powershell-exit-codes-always-0 + +trap +{ + Write-Error -ErrorRecord $_ + exit 1; +} + +throw "Oh noes I has an error" diff --git a/test/integration/roles/test_win_script/tasks/main.yml b/test/integration/roles/test_win_script/tasks/main.yml new file mode 100644 index 0000000000..99d94387f6 --- /dev/null +++ b/test/integration/roles/test_win_script/tasks/main.yml @@ -0,0 +1,59 @@ +--- + +- name: run simple test script + script: test_script.ps1 + register: test_script_result + +- name: check that script ran + assert: + that: + - "test_script_result.rc == 0" + - "test_script_result.stdout" + - "'Woohoo' in test_script_result.stdout" + - "not test_script_result.stderr" + - "not test_script_result|failed" + - "test_script_result|changed" + +- name: run test script that takes arguments + script: test_script_with_args.ps1 /this /that /other + register: test_script_with_args_result + +- name: check that script ran and received arguments + assert: + that: + - "test_script_with_args_result.rc == 0" + - "test_script_with_args_result.stdout" + - "test_script_with_args_result.stdout_lines[0] == '/this'" + - "test_script_with_args_result.stdout_lines[1] == '/that'" + - "test_script_with_args_result.stdout_lines[2] == '/other'" + - "not test_script_with_args_result.stderr" + - "not test_script_with_args_result|failed" + - "test_script_with_args_result|changed" + +- name: run test script that has errors + script: test_script_with_errors.ps1 + register: test_script_with_errors_result + ignore_errors: true + +- name: check that script ran but failed with errors + assert: + that: + - "test_script_with_errors_result.rc != 0" + - "not test_script_with_errors_result.stdout" + - "test_script_with_errors_result.stderr" + - "test_script_with_errors_result|failed" + - "test_script_with_errors_result|changed" + +- name: run simple batch file + script: test_script.bat + register: test_batch_result + +- name: check that batch file ran + assert: + that: + - "test_batch_result.rc == 0" + - "test_batch_result.stdout" + - "'batch' in test_batch_result.stdout" + - "not test_batch_result.stderr" + - "not test_batch_result|failed" + - "test_batch_result|changed" diff --git a/test/integration/test_winrm.yml b/test/integration/test_winrm.yml new file mode 100644 index 0000000000..9ce95fc304 --- /dev/null +++ b/test/integration/test_winrm.yml @@ -0,0 +1,8 @@ +--- + +- hosts: windows + gather_facts: false + roles: + - { role: test_win_raw, tags: test_win_raw } + - { role: test_win_script, tags: test_win_script } + - { role: test_win_ping, tags: test_win_ping } From 7e8cc65829854f72d1165f7e346c909d490386a8 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 17 Jun 2014 23:26:36 -0500 Subject: [PATCH 016/107] Refactor common args used for building PowerShell commands. --- lib/ansible/runner/connection_plugins/winrm.py | 2 +- lib/ansible/runner/shell_plugins/powershell.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/ansible/runner/connection_plugins/winrm.py b/lib/ansible/runner/connection_plugins/winrm.py index 5b6a019085..7300b182b6 100644 --- a/lib/ansible/runner/connection_plugins/winrm.py +++ b/lib/ansible/runner/connection_plugins/winrm.py @@ -127,7 +127,7 @@ class Connection(object): vvvv("WINRM PARTS %r" % cmd_parts, host=self.host) # For script/raw support. if cmd_parts and cmd_parts[0].lower().endswith('.ps1'): - script = 'PowerShell -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File ' + ' '.join(['"%s"' % x for x in cmd_parts]) + script = powershell._build_file_cmd(cmd_parts) cmd_parts = powershell._encode_script(script, as_list=True) try: result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) diff --git a/lib/ansible/runner/shell_plugins/powershell.py b/lib/ansible/runner/shell_plugins/powershell.py index 75f1a20478..1d6c74b587 100644 --- a/lib/ansible/runner/shell_plugins/powershell.py +++ b/lib/ansible/runner/shell_plugins/powershell.py @@ -22,6 +22,8 @@ import random import shlex import time +_common_args = ['PowerShell', '-NoProfile', '-NonInteractive'] + def _escape(value, include_vars=False): '''Return value escaped for use in PowerShell command.''' # http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences @@ -39,11 +41,15 @@ def _escape(value, include_vars=False): def _encode_script(script, as_list=False): '''Convert a PowerShell script to a single base64-encoded command.''' encoded_script = base64.b64encode(script.encode('utf-16-le')) - cmd_parts = ['PowerShell', '-NoProfile', '-NonInteractive', '-EncodedCommand', encoded_script] + cmd_parts = _common_args + ['-EncodedCommand', encoded_script] if as_list: return cmd_parts return ' '.join(cmd_parts) +def _build_file_cmd(cmd_parts): + '''Build command line to run a file, given list of file name plus args.''' + return ' '.join(_common_args + ['-ExecutionPolicy', 'Unrestricted', '-File'] + ['"%s"' % x for x in cmd_parts]) + class ShellModule(object): def env_prefix(self, **kwargs): @@ -75,8 +81,7 @@ class ShellModule(object): cmd_parts = shlex.split(cmd, posix=False) if not cmd_parts[0].lower().endswith('.ps1'): cmd_parts[0] = '%s.ps1' % cmd_parts[0] - cmd_parts = ['PowerShell', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Unrestricted', '-File'] + ['"%s"' % x for x in cmd_parts] - script = ' '.join(cmd_parts) + script = _build_file_cmd(cmd_parts) if rm_tmp: rm_tmp = _escape(rm_tmp) script = '%s; Remove-Item "%s" -Force -Recurse;' % (script, rm_tmp) From 09e538f954712f41feb64eaef2617137adf590ea Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 09:32:02 -0500 Subject: [PATCH 017/107] Add start for powershell setup module --- library/windows/assemble.ps1 | 6 +-- library/windows/async_wrapper.ps1 | 6 +-- library/windows/command.ps1 | 6 +-- library/windows/copy.ps1 | 6 +-- library/windows/file.ps1 | 6 +-- library/windows/ping.ps1 | 6 +-- library/windows/setup.ps1 | 71 +++++++++++++++++++++++++++++++ library/windows/slurp.ps1 | 6 +-- library/windows/stat.ps1 | 6 +-- library/windows/win_ping.ps1 | 6 +-- 10 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 library/windows/setup.ps1 diff --git a/library/windows/assemble.ps1 b/library/windows/assemble.ps1 index e5c4a01907..c4627bb48a 100644 --- a/library/windows/assemble.ps1 +++ b/library/windows/assemble.ps1 @@ -1,7 +1,4 @@ #!powershell -# WANT_JSON -# POWERSHELL_COMMON - # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# WANT_JSON +# POWERSHELL_COMMON + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/async_wrapper.ps1 b/library/windows/async_wrapper.ps1 index e5c4a01907..c4627bb48a 100644 --- a/library/windows/async_wrapper.ps1 +++ b/library/windows/async_wrapper.ps1 @@ -1,7 +1,4 @@ #!powershell -# WANT_JSON -# POWERSHELL_COMMON - # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# WANT_JSON +# POWERSHELL_COMMON + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/command.ps1 b/library/windows/command.ps1 index e5c4a01907..c4627bb48a 100644 --- a/library/windows/command.ps1 +++ b/library/windows/command.ps1 @@ -1,7 +1,4 @@ #!powershell -# WANT_JSON -# POWERSHELL_COMMON - # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# WANT_JSON +# POWERSHELL_COMMON + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/copy.ps1 b/library/windows/copy.ps1 index e5c4a01907..c4627bb48a 100644 --- a/library/windows/copy.ps1 +++ b/library/windows/copy.ps1 @@ -1,7 +1,4 @@ #!powershell -# WANT_JSON -# POWERSHELL_COMMON - # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# WANT_JSON +# POWERSHELL_COMMON + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/file.ps1 b/library/windows/file.ps1 index e5c4a01907..c4627bb48a 100644 --- a/library/windows/file.ps1 +++ b/library/windows/file.ps1 @@ -1,7 +1,4 @@ #!powershell -# WANT_JSON -# POWERSHELL_COMMON - # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# WANT_JSON +# POWERSHELL_COMMON + $params = Parse-Args $args; $data = 'FIXME'; diff --git a/library/windows/ping.ps1 b/library/windows/ping.ps1 index 74311119c5..6d1af14e9e 100644 --- a/library/windows/ping.ps1 +++ b/library/windows/ping.ps1 @@ -1,7 +1,4 @@ #!powershell -# WANT_JSON -# POWERSHELL_COMMON - # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# WANT_JSON +# POWERSHELL_COMMON + $params = Parse-Args $args; $data = 'pong'; diff --git a/library/windows/setup.ps1 b/library/windows/setup.ps1 new file mode 100644 index 0000000000..9b77487c42 --- /dev/null +++ b/library/windows/setup.ps1 @@ -0,0 +1,71 @@ +#!powershell +# 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 . + +# WANT_JSON +# POWERSHELL_COMMON + +$params = Parse-Args $args; + +$fact_path = "C:\ansible\facts.d" +If ($params.fact_path.GetType) +{ + $fact_path = $params.fact_path; +} + +$filter = "*" +If ($params.filter.GetType) +{ + $filter = $params.filter; +} + +$result = New-Object psobject @{ + ansible_facts = New-Object psobject + changed = $false +}; + +If (Test-Path $fact_path) +{ + Get-ChildItem $fact_path -Filter *.fact | Foreach-Object + { + $facts = Get-Content $_ | ConvertFrom-Json + # TODO: Need to concatentate this with $result + } +} + +$osversion = [Environment]::OSVersion + +Set-Attr $result.ansible_facts "ansible_hostname" $env:COMPUTERNAME; +Set-Attr $result.ansible_facts "ansible_fqdn" "$([System.Net.Dns]::GetHostByName((hostname)).HostName)" +Set-Attr $result.ansible_facts "ansible_system" $osversion.Platform +Set-Attr $result.ansible_facts "ansible_os_family" "Windows" +Set-Attr $result.ansible_facts "ansible_distribution" $osversion.VersionString +Set-Attr $result.ansible_facts "ansible_distribution_version" $osversion.Version.ToString() + +# Need to figure out how to filter the code. Below is a start but not implemented +#If ($filter != "*") +#{ +# $filtered = New-Object psobject; +# $result.psobject.properties | Where +# { +# $_.Name -like $filter | +# } +#} +#Else +#{ +# $filtered = $result; +#} + +echo $result | ConvertTo-Json; diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 index 6f001a4924..8fedcdc2f9 100644 --- a/library/windows/slurp.ps1 +++ b/library/windows/slurp.ps1 @@ -1,7 +1,4 @@ #!powershell -# WANT_JSON -# POWERSHELL_COMMON - # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# WANT_JSON +# POWERSHELL_COMMON + $params = Parse-Args $args; $src = ''; diff --git a/library/windows/stat.ps1 b/library/windows/stat.ps1 index bf422858ab..50a46064d2 100644 --- a/library/windows/stat.ps1 +++ b/library/windows/stat.ps1 @@ -1,7 +1,4 @@ #!powershell -# WANT_JSON -# POWERSHELL_COMMON - # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# WANT_JSON +# POWERSHELL_COMMON + $params = Parse-Args $args; $path = ''; diff --git a/library/windows/win_ping.ps1 b/library/windows/win_ping.ps1 index 74311119c5..6d1af14e9e 100644 --- a/library/windows/win_ping.ps1 +++ b/library/windows/win_ping.ps1 @@ -1,7 +1,4 @@ #!powershell -# WANT_JSON -# POWERSHELL_COMMON - # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +# WANT_JSON +# POWERSHELL_COMMON + $params = Parse-Args $args; $data = 'pong'; From 02caebb08fdccdf3d82540882fedb9fc78ab4811 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 09:35:33 -0500 Subject: [PATCH 018/107] Rename some modules so they can have seperate doc stubs. --- library/windows/ping.ps1 | 30 ---------------------- library/windows/{stat.ps1 => win_stat.ps1} | 0 2 files changed, 30 deletions(-) delete mode 100644 library/windows/ping.ps1 rename library/windows/{stat.ps1 => win_stat.ps1} (100%) diff --git a/library/windows/ping.ps1 b/library/windows/ping.ps1 deleted file mode 100644 index 6d1af14e9e..0000000000 --- a/library/windows/ping.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -#!powershell -# 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 . - -# WANT_JSON -# POWERSHELL_COMMON - -$params = Parse-Args $args; - -$data = 'pong'; -If ($params.data.GetType) -{ - $data = $params.data; -} - -$result = New-Object psobject; -Set-Attr $result "ping" $data; -echo $result | ConvertTo-Json; diff --git a/library/windows/stat.ps1 b/library/windows/win_stat.ps1 similarity index 100% rename from library/windows/stat.ps1 rename to library/windows/win_stat.ps1 From f92f73936602f6f8cfee0672c12f29a4f008fa3a Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 09:36:10 -0500 Subject: [PATCH 019/107] Need to call ToString Platform to get the string --- library/windows/setup.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/windows/setup.ps1 b/library/windows/setup.ps1 index 9b77487c42..585be6317e 100644 --- a/library/windows/setup.ps1 +++ b/library/windows/setup.ps1 @@ -49,7 +49,7 @@ $osversion = [Environment]::OSVersion Set-Attr $result.ansible_facts "ansible_hostname" $env:COMPUTERNAME; Set-Attr $result.ansible_facts "ansible_fqdn" "$([System.Net.Dns]::GetHostByName((hostname)).HostName)" -Set-Attr $result.ansible_facts "ansible_system" $osversion.Platform +Set-Attr $result.ansible_facts "ansible_system" $osversion.Platform.ToString() Set-Attr $result.ansible_facts "ansible_os_family" "Windows" Set-Attr $result.ansible_facts "ansible_distribution" $osversion.VersionString Set-Attr $result.ansible_facts "ansible_distribution_version" $osversion.Version.ToString() From 4991c77479246e0eed3511597746fcc54acd65b2 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 09:39:03 -0500 Subject: [PATCH 020/107] Remove stubs. --- library/windows/assemble.ps1 | 26 -------------------------- library/windows/async_wrapper.ps1 | 26 -------------------------- library/windows/command.ps1 | 26 -------------------------- library/windows/copy.ps1 | 26 -------------------------- library/windows/file.ps1 | 26 -------------------------- 5 files changed, 130 deletions(-) delete mode 100644 library/windows/assemble.ps1 delete mode 100644 library/windows/async_wrapper.ps1 delete mode 100644 library/windows/command.ps1 delete mode 100644 library/windows/copy.ps1 delete mode 100644 library/windows/file.ps1 diff --git a/library/windows/assemble.ps1 b/library/windows/assemble.ps1 deleted file mode 100644 index c4627bb48a..0000000000 --- a/library/windows/assemble.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -#!powershell -# 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 . - -# WANT_JSON -# POWERSHELL_COMMON - -$params = Parse-Args $args; - -$data = 'FIXME'; - -$result = New-Object psobject; -Set-Attr $result "fixme" $data; -echo $result | ConvertTo-Json; diff --git a/library/windows/async_wrapper.ps1 b/library/windows/async_wrapper.ps1 deleted file mode 100644 index c4627bb48a..0000000000 --- a/library/windows/async_wrapper.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -#!powershell -# 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 . - -# WANT_JSON -# POWERSHELL_COMMON - -$params = Parse-Args $args; - -$data = 'FIXME'; - -$result = New-Object psobject; -Set-Attr $result "fixme" $data; -echo $result | ConvertTo-Json; diff --git a/library/windows/command.ps1 b/library/windows/command.ps1 deleted file mode 100644 index c4627bb48a..0000000000 --- a/library/windows/command.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -#!powershell -# 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 . - -# WANT_JSON -# POWERSHELL_COMMON - -$params = Parse-Args $args; - -$data = 'FIXME'; - -$result = New-Object psobject; -Set-Attr $result "fixme" $data; -echo $result | ConvertTo-Json; diff --git a/library/windows/copy.ps1 b/library/windows/copy.ps1 deleted file mode 100644 index c4627bb48a..0000000000 --- a/library/windows/copy.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -#!powershell -# 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 . - -# WANT_JSON -# POWERSHELL_COMMON - -$params = Parse-Args $args; - -$data = 'FIXME'; - -$result = New-Object psobject; -Set-Attr $result "fixme" $data; -echo $result | ConvertTo-Json; diff --git a/library/windows/file.ps1 b/library/windows/file.ps1 deleted file mode 100644 index c4627bb48a..0000000000 --- a/library/windows/file.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -#!powershell -# 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 . - -# WANT_JSON -# POWERSHELL_COMMON - -$params = Parse-Args $args; - -$data = 'FIXME'; - -$result = New-Object psobject; -Set-Attr $result "fixme" $data; -echo $result | ConvertTo-Json; From 21ba529fbecee26c28ade823c8509f2c180706ce Mon Sep 17 00:00:00 2001 From: Chris Church Date: Wed, 18 Jun 2014 10:01:11 -0500 Subject: [PATCH 021/107] Fixes/notes related to slashes in remote paths. --- lib/ansible/runner/action_plugins/copy.py | 12 ++++++------ lib/ansible/runner/action_plugins/fetch.py | 2 +- lib/ansible/runner/action_plugins/template.py | 2 +- lib/ansible/runner/action_plugins/unarchive.py | 2 +- lib/ansible/runner/shell_plugins/powershell.py | 4 ++++ lib/ansible/runner/shell_plugins/sh.py | 3 +++ 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/ansible/runner/action_plugins/copy.py b/lib/ansible/runner/action_plugins/copy.py index d84b3f550e..c59042fb2b 100644 --- a/lib/ansible/runner/action_plugins/copy.py +++ b/lib/ansible/runner/action_plugins/copy.py @@ -136,8 +136,8 @@ class ActionModule(object): # If it's recursive copy, destination is always a dir, # explicitly mark it so (note - copy module relies on this). - if not dest.endswith("/"): - dest += "/" + if not conn.shell.path_has_trailing_slash(dest): + dest = conn.shell.join_path(dest, '') else: source_files.append((source, os.path.basename(source))) @@ -169,10 +169,10 @@ class ActionModule(object): # This is kind of optimization - if user told us destination is # dir, do path manipulation right away, otherwise we still check # for dest being a dir via remote call below. - if dest.endswith("/"): # CCTODO: Fixme for powershell - dest_file = os.path.join(dest, source_rel) + if conn.shell.path_has_trailing_slash(dest): + dest_file = conn.shell.join_path(dest, source_rel) else: - dest_file = dest + dest_file = conn.shell.join_path(dest) # Attempt to get the remote MD5 Hash. remote_md5 = self.runner._remote_md5(conn, tmp_path, dest_file) @@ -186,7 +186,7 @@ class ActionModule(object): return ReturnData(conn=conn, result=result) else: # Append the relative source location to the destination and retry remote_md5. - dest_file = os.path.join(dest, source_rel) # CCTODO + dest_file = conn.shell.join_path(dest, source_rel) remote_md5 = self.runner._remote_md5(conn, tmp_path, dest_file) if remote_md5 != '1' and not force: diff --git a/lib/ansible/runner/action_plugins/fetch.py b/lib/ansible/runner/action_plugins/fetch.py index 2d7f7e974d..1600e8803c 100644 --- a/lib/ansible/runner/action_plugins/fetch.py +++ b/lib/ansible/runner/action_plugins/fetch.py @@ -59,7 +59,7 @@ class ActionModule(object): source = os.path.expanduser(source) if flat: - if dest.endswith("/"): # CCTODO + if dest.endswith("/"): # CCTODO: Fix path for Windows hosts. # if the path ends with "/", we'll use the source filename as the # destination filename base = os.path.basename(source) diff --git a/lib/ansible/runner/action_plugins/template.py b/lib/ansible/runner/action_plugins/template.py index 774a2be6ff..623d173c09 100644 --- a/lib/ansible/runner/action_plugins/template.py +++ b/lib/ansible/runner/action_plugins/template.py @@ -79,7 +79,7 @@ class ActionModule(object): source = utils.path_dwim(self.runner.basedir, source) - if dest.endswith("/"): # CCTODO + if dest.endswith("/"): # CCTODO: Fix path for Windows hosts. base = os.path.basename(source) dest = os.path.join(dest, base) diff --git a/lib/ansible/runner/action_plugins/unarchive.py b/lib/ansible/runner/action_plugins/unarchive.py index be0070fca1..16c0bc8117 100644 --- a/lib/ansible/runner/action_plugins/unarchive.py +++ b/lib/ansible/runner/action_plugins/unarchive.py @@ -54,7 +54,7 @@ class ActionModule(object): result = dict(failed=True, msg="src (or content) and dest are required") return ReturnData(conn=conn, result=result) - dest = os.path.expanduser(dest) + dest = os.path.expanduser(dest) # CCTODO: Fix path for Windows hosts. source = template.template(self.runner.basedir, os.path.expanduser(source), inject) if copy: if '_original_file' in inject: diff --git a/lib/ansible/runner/shell_plugins/powershell.py b/lib/ansible/runner/shell_plugins/powershell.py index 1d6c74b587..031077215e 100644 --- a/lib/ansible/runner/shell_plugins/powershell.py +++ b/lib/ansible/runner/shell_plugins/powershell.py @@ -58,6 +58,10 @@ class ShellModule(object): def join_path(self, *args): return os.path.join(*args).replace('/', '\\') + def path_has_trailing_slash(self, path): + # Allow Windows paths to be specified using either slash. + return path.endswith('/') or path.endswith('\\') + def chmod(self, mode, path): return '' diff --git a/lib/ansible/runner/shell_plugins/sh.py b/lib/ansible/runner/shell_plugins/sh.py index 8b74386d85..1ee225830b 100644 --- a/lib/ansible/runner/shell_plugins/sh.py +++ b/lib/ansible/runner/shell_plugins/sh.py @@ -33,6 +33,9 @@ class ShellModule(object): def join_path(self, *args): return os.path.join(*args) + def path_has_trailing_slash(self, path): + return path.endswith('/') + def chmod(self, mode, path): path = pipes.quote(path) return 'chmod %s %s' % (mode, path) From c0c9ff23b2d887fbb2c414bf41c6e401722b873f Mon Sep 17 00:00:00 2001 From: Chris Church Date: Wed, 18 Jun 2014 10:07:35 -0500 Subject: [PATCH 022/107] Fix win_ping integration test. --- .../roles/test_win_ping/tasks/main.yml | 58 ++++++++----------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/test/integration/roles/test_win_ping/tasks/main.yml b/test/integration/roles/test_win_ping/tasks/main.yml index f3b5fcc4f4..14c517cfa8 100644 --- a/test/integration/roles/test_win_ping/tasks/main.yml +++ b/test/integration/roles/test_win_ping/tasks/main.yml @@ -22,45 +22,35 @@ - "not win_ping_with_data_result|changed" - "win_ping_with_data_result.ping == 'blah'" -- name: test ping.ps1 with data - ping.ps1: data=bleep - register: ping_ps1_result +#- name: test local ping (should use default ping) +# local_action: ping +# register: local_ping_result -- name: check ping.ps1 result +#- name: check local ping result +# assert: +# that: +# - "not local_ping_result|failed" +# - "not local_ping_result|changed" +# - "local_ping_result.ping == 'pong'" + +- name: test win_ping.ps1 with data + win_ping.ps1: data=bleep + register: win_ping_ps1_result + +- name: check win_ping.ps1 result with data assert: that: - - "not ping_ps1_result|failed" - - "not ping_ps1_result|changed" - - "ping_ps1_result.ping == 'bleep'" + - "not win_ping_ps1_result|failed" + - "not win_ping_ps1_result|changed" + - "win_ping_ps1_result.ping == 'bleep'" -#- name: test ping.ps1 with invalid args -# ping.ps1: arg=invalid -# register: ping_ps1_invalid_args_result +#- name: test win_ping with invalid args +# win_ping: arg=invalid +# register: win_ping_ps1_invalid_args_result -#- name: check that ping.ps1 with invalid args fails +#- name: check that win_ping.ps1 with invalid args fails # assert: # that: -# - "ping_ps1_invalid_args_result|failed" -# - "ping_ps1_invalid_args_result.msg" +# - "win_ping_ps1_invalid_args_result|failed" +# - "win_ping_ps1_invalid_args_result.msg" -- name: test local ping (should use default ping) - local_action: ping - register: local_ping_result - -- name: check local ping result - assert: - that: - - "not local_ping_result|failed" - - "not local_ping_result|changed" - - "local_ping_result.ping == 'pong'" - -- name: test ping (should use ping.ps1) - action: ping - register: ping_result - -- name: check ping result - assert: - that: - - "not ping_result|failed" - - "not ping_result|changed" - - "ping_result.ping == 'pong'" From 4f764fd3e76a041d9056d66460dcbca1a1cb4976 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 10:07:46 -0500 Subject: [PATCH 023/107] setup.ps1 willnot support fact_path and filter to start --- library/windows/setup.ps1 | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/library/windows/setup.ps1 b/library/windows/setup.ps1 index 585be6317e..883c279205 100644 --- a/library/windows/setup.ps1 +++ b/library/windows/setup.ps1 @@ -17,34 +17,14 @@ # WANT_JSON # POWERSHELL_COMMON -$params = Parse-Args $args; - -$fact_path = "C:\ansible\facts.d" -If ($params.fact_path.GetType) -{ - $fact_path = $params.fact_path; -} - -$filter = "*" -If ($params.filter.GetType) -{ - $filter = $params.filter; -} +# $params is not currently used in this module +# $params = Parse-Args $args; $result = New-Object psobject @{ ansible_facts = New-Object psobject changed = $false }; -If (Test-Path $fact_path) -{ - Get-ChildItem $fact_path -Filter *.fact | Foreach-Object - { - $facts = Get-Content $_ | ConvertFrom-Json - # TODO: Need to concatentate this with $result - } -} - $osversion = [Environment]::OSVersion Set-Attr $result.ansible_facts "ansible_hostname" $env:COMPUTERNAME; @@ -54,18 +34,4 @@ Set-Attr $result.ansible_facts "ansible_os_family" "Windows" Set-Attr $result.ansible_facts "ansible_distribution" $osversion.VersionString Set-Attr $result.ansible_facts "ansible_distribution_version" $osversion.Version.ToString() -# Need to figure out how to filter the code. Below is a start but not implemented -#If ($filter != "*") -#{ -# $filtered = New-Object psobject; -# $result.psobject.properties | Where -# { -# $_.Name -like $filter | -# } -#} -#Else -#{ -# $filtered = $result; -#} - echo $result | ConvertTo-Json; From e4ff74ebbd9dcff60edcec7e5405ceca2e6ca789 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 09:50:12 -0500 Subject: [PATCH 024/107] Add a docs stub for the module. --- library/windows/win_ping | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 library/windows/win_ping diff --git a/library/windows/win_ping b/library/windows/win_ping new file mode 100644 index 0000000000..72b0dbc12a --- /dev/null +++ b/library/windows/win_ping @@ -0,0 +1,38 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2012, Michael DeHaan , and others +# +# 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 . + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +DOCUMENTATION = ''' +--- +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 +author: Michael DeHaan +''' + +EXAMPLES = ''' +# Example from an Ansible Playbook +- action: win_ping +''' + From 618e8dee7861d2a311007804d3f5c6f7d8055eeb Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 09:55:07 -0500 Subject: [PATCH 025/107] Add doc stubs --- library/windows/win_ping | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/windows/win_ping b/library/windows/win_ping index 72b0dbc12a..9ec649b7e4 100644 --- a/library/windows/win_ping +++ b/library/windows/win_ping @@ -28,7 +28,7 @@ version_added: "1.7" short_description: A windows version of the classic ping module. description: - Checks management connectivity of a windows host -author: Michael DeHaan +author: Chris Church ''' EXAMPLES = ''' From e3cc1eaefcfa8d8fe463f2d4c0945d0c8b2ef648 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 10:10:36 -0500 Subject: [PATCH 026/107] Update windows docs in progress. --- docsite/rst/intro_windows.rst | 82 ++++++++++++++++++++++++++++++++--- library/windows/win_stat | 51 ++++++++++++++++++++++ 2 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 library/windows/win_stat diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 584d66d9e5..aa6d51e0bb 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -6,13 +6,16 @@ Windows Support .. _windows_how_does_it_work: Windows: How Does It Work -`````````````````````````` +````````````````````````` -Ansible manages Linux/Unix machines using SSH by default. +As you may have already read, Ansible manages Linux/Unix machines using SSH by default. -Starting in version 1.7, Ansible also contains support for managing Windows machines. +Starting in version 1.7, Ansible also contains support for managing Windows machines. This uses +native powershell remoting, rather than SSH. -Ansible will still run from a Linux guest, and uses the "winrm" Python module to talk to remote hosts. +Ansible will still be run from a Linux guest, and uses the "winrm" Python module to talk to remote hosts. + +No additional software needs to be installed on the remote machines for Ansible to manage them, it still maintains the agentless properties that make it popular on Linux/Unix. .. _windows_installing: @@ -28,9 +31,7 @@ On a Linux control machine:: Inventory ````````` -Ansible's windows support will rely on a few standard variables to indicate the username, password, and connection type (windows) -of the remote hosts:: - +Ansible's windows support relies on a few standard variables to indicate the username, password, and connection type (windows) of the remote hosts. These variables are most easily set up in inventory. This is used instead of SSH-keys or passwords as normally fed into Ansible. [windows] winserver1.example.com @@ -44,6 +45,8 @@ In group_vars/windows.yml, define the following inventory variables:: ansible_ssh_pass: SekritPasswordGoesHere ansible_ssh_port: 5986 ansible_connection: winrm + +Notice that the ssh_port is not actually for SSH, but this is a holdover from how Ansible is mostly an SSH-oriented system. Again, Windows management will not happen over SSH. When using your playbook, don't forget to specify --ask-vault-pass to provide the password to unlock the file. @@ -120,6 +123,71 @@ will be required to manage Windows hosts. Cygwin is not supported, so please do not ask questions about Ansible running from Cygwin. +.. _windows_facts: + +Windows Facts +````````````` + +Just as with Linux/Unix, facts can be gathered for windows hosts, which will return things such as the operating system version. To see what variables are available about a windows host, run the following:: + + ansible winhost.example.com -m setup + +Note that this command invocation is exactly the same as the Linux/Unix equivalent. + +.. _windows_playbook_example: + +Windows Playbook Examples +````````````````````````` + +Look to the list of windows modules for most of what is possible, though also some modules like "raw" and "script" also work on Windows, as do "fetch" and "slurp". + +Here is an example of pushing and running a powershell script:: + + - name: test script module + hosts: windows + tasks: + - name: run test script + script: files/test_script.ps1 + +Running individual commands uses the 'raw' module, as opposed to the shell or command module as is common on Linux/Unix operating systems:: + + - name: test raw module + hosts: windows + tasks: + - name: run ipconfig + raw: ipconfig + register: ipconfig + - debug: var=ipconfig + +And for a final example, here's how to use the win_stat module to test for file existance. Note that the data returned byt he win_stat module is slightly different than what is provided by the Linux equivalent. + + - name: test stat module + hosts: windows + tasks: + - name: test stat module on file + win_stat: path="C:/Windows/win.ini" + register: stat_file + + - debug: var=stat_file + + - name: check stat_file result + assert: + that: + - "stat_file.stat.exists" + - "not stat_file.stat.isdir" + - "stat_file.stat.size > 0" + - "stat_file.stat.md5" + +Again, recall that the Windows modules are all listed in the Windows category of modules, with the exception that the "raw", "script", and "fetch" modules are also available. These modules do not start with a "win_" prefix. + +.. _windows_contributions: + +Windows Contributions +````````````````````` + +Windows support in Ansible is still very new, and contributions are quite welcome, whether this is in the +form of new modules, tweaks to existing modules, documentation, or something else. Please stop by the ansible-devel mailing list if you would like to get involved and say hi. + .. seealso:: :doc:`developing_modules` diff --git a/library/windows/win_stat b/library/windows/win_stat new file mode 100644 index 0000000000..d591663da4 --- /dev/null +++ b/library/windows/win_stat @@ -0,0 +1,51 @@ +#!/usr/bin/python +# 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 . + +# this is a windows documentation stub, actual code lives in the .ps1 +# file of the same name + +DOCUMENTATION = ''' +--- +module: win_stat +version_added: "1.7" +short_description: returns information about a Windows file +description: + - Returns information about a Windows file +options: + path: + description: + - The full path of the file/object to get the facts of + required: true + default: null + aliases: [] + get_md5: + description: + - Whether to return the md5 sum of the file + required: false + default: yes + aliases: [] +author: Chris Church +''' + +EXAMPLES = ''' +# Obtain information about a file + +- win_stat: path=C:\foo.ini + register: file_info + +- debug: var=file_info +''' + From 9ca83446a2dd3097b0d5afe888ceb03bd84e4e89 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 10:31:41 -0500 Subject: [PATCH 027/107] A couple of DOCUMENTATION fixes for win_ping and win_stat --- library/windows/win_ping | 1 + library/windows/win_stat | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/library/windows/win_ping b/library/windows/win_ping index 9ec649b7e4..4e7e32c45a 100644 --- a/library/windows/win_ping +++ b/library/windows/win_ping @@ -28,6 +28,7 @@ version_added: "1.7" short_description: A windows version of the classic ping module. description: - Checks management connectivity of a windows host +options: {} author: Chris Church ''' diff --git a/library/windows/win_stat b/library/windows/win_stat index d591663da4..5aba801a67 100644 --- a/library/windows/win_stat +++ b/library/windows/win_stat @@ -43,7 +43,7 @@ author: Chris Church EXAMPLES = ''' # Obtain information about a file -- win_stat: path=C:\foo.ini +- win_stat: path=C:\\foo.ini register: file_info - debug: var=file_info From 8f762a7d1575ad1fa6723ae4dc9b4b73601d5592 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Wed, 18 Jun 2014 10:43:15 -0500 Subject: [PATCH 028/107] Update logging based on verbosity, add vvvvv support to show details of put/fetch file. --- .../runner/connection_plugins/winrm.py | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/ansible/runner/connection_plugins/winrm.py b/lib/ansible/runner/connection_plugins/winrm.py index 7300b182b6..5bf0467f7d 100644 --- a/lib/ansible/runner/connection_plugins/winrm.py +++ b/lib/ansible/runner/connection_plugins/winrm.py @@ -27,7 +27,7 @@ import traceback import urlparse from ansible import errors from ansible import utils -from ansible.callbacks import vvv, vvvv +from ansible.callbacks import vvv, vvvv, verbose from ansible.runner.shell_plugins import powershell try: @@ -41,6 +41,9 @@ _winrm_cache = { # 'user:pwhash@host:port': } +def vvvvv(msg, host=None): + verbose(msg, host=None, caplevel=4) + class Connection(object): '''WinRM connections over HTTP/HTTPS.''' @@ -98,8 +101,11 @@ class Connection(object): if exc: raise exc - def _winrm_exec(self, command, args): - vvvv("WINRM EXEC %r %r" % (command, args), host=self.host) + def _winrm_exec(self, command, args, from_exec=False): + if from_exec: + vvvv("WINRM EXEC %r %r" % (command, args), host=self.host) + else: + vvvvv("WINRM EXEC %r %r" % (command, args), host=self.host) if not self.protocol: self.protocol = self._winrm_connect() if not self.shell_id: @@ -108,8 +114,12 @@ class Connection(object): try: command_id = self.protocol.run_command(self.shell_id, command, args) response = Response(self.protocol.get_command_output(self.shell_id, command_id)) - vvvv('WINRM RESULT %r' % response, host=self.host) - vvvv('WINRM STDERR %s' % response.std_err, host=self.host) + if from_exec: + vvvv('WINRM RESULT %r' % response, host=self.host) + else: + vvvvv('WINRM RESULT %r' % response, host=self.host) + vvvvv('WINRM STDOUT %s' % response.std_out, host=self.host) + vvvvv('WINRM STDERR %s' % response.std_err, host=self.host) return response finally: if command_id: @@ -122,15 +132,19 @@ class Connection(object): def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable=None, in_data=None, su=None, su_user=None): cmd = cmd.encode('utf-8') - vvv("EXEC %s" % cmd, host=self.host) cmd_parts = shlex.split(cmd, posix=False) - vvvv("WINRM PARTS %r" % cmd_parts, host=self.host) + if '-EncodedCommand' in cmd_parts: + encoded_cmd = cmd_parts[cmd_parts.index('-EncodedCommand') + 1] + decoded_cmd = base64.b64decode(encoded_cmd) + vvv("EXEC %s" % decoded_cmd, host=self.host) + else: + vvv("EXEC %s" % cmd, host=self.host) # For script/raw support. if cmd_parts and cmd_parts[0].lower().endswith('.ps1'): script = powershell._build_file_cmd(cmd_parts) cmd_parts = powershell._encode_script(script, as_list=True) try: - result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) + result = self._winrm_exec(cmd_parts[0], cmd_parts[1:], from_exec=True) except Exception, e: traceback.print_exc() raise errors.AnsibleError("failed to exec cmd %s" % cmd) @@ -160,6 +174,7 @@ class Connection(object): $stream.SetLength(%d) | Out-Null; $stream.Close() | Out-Null; ''' % (buffer_size, powershell._escape(out_path), offset, b64_data, in_size) + vvvv("WINRM PUT %s to %s (offset=%d size=%d)" % (in_path, out_path, offset, len(out_data)), host=self.host) cmd_parts = powershell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) if result.status_code != 0: @@ -188,6 +203,7 @@ class Connection(object): [System.Convert]::ToBase64String($bytes); $stream.Close() | Out-Null; ''' % (buffer_size, powershell._escape(in_path), offset) + vvvv("WINRM FETCH %s to %s (offset=%d)" % (in_path, out_path, offset), host=self.host) cmd_parts = powershell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) data = base64.b64decode(result.std_out.strip()) From bf400d3c23c1fd4d74fe00ffe6d354bfede1295d Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 10:42:55 -0500 Subject: [PATCH 029/107] Mention that with windows target hosts, fact_path and filter are not currently supported --- library/system/setup | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/system/setup b/library/system/setup index cc3a5855f1..c5b408afca 100644 --- a/library/system/setup +++ b/library/system/setup @@ -54,6 +54,8 @@ notes: install I(facter) and I(ohai) means you can avoid Ruby-dependencies on your remote systems. (See also M(facter) and M(ohai).) - The filter option filters only the first level subkey below ansible_facts. + - If the target host is Windows, you will not currently have the ability to use + C(fact_path) or C(filter). author: Michael DeHaan ''' From 51a50dee3ed169dacfbc8821e931c068b2e5f62e Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 10:57:27 -0500 Subject: [PATCH 030/107] Clean up how we initialize the result psobject --- library/windows/slurp.ps1 | 6 ++++-- library/windows/win_ping.ps1 | 6 ++++-- library/windows/win_stat.ps1 | 25 +++++++++++++------------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 index 8fedcdc2f9..d224539ee6 100644 --- a/library/windows/slurp.ps1 +++ b/library/windows/slurp.ps1 @@ -39,7 +39,9 @@ If (-not $src) $bytes = [System.IO.File]::ReadAllBytes($src); $content = [System.Convert]::ToBase64String($bytes); -$result = New-Object psobject; +$result = New-Object psobject @{ + changed = $false + encoding = "base64" +}; Set-Attr $result "content" $content; -Set-Attr $result "encoding" "base64"; echo $result | ConvertTo-Json; diff --git a/library/windows/win_ping.ps1 b/library/windows/win_ping.ps1 index 6d1af14e9e..0771ea3125 100644 --- a/library/windows/win_ping.ps1 +++ b/library/windows/win_ping.ps1 @@ -25,6 +25,8 @@ If ($params.data.GetType) $data = $params.data; } -$result = New-Object psobject; -Set-Attr $result "ping" $data; +$result = New-Object psobject @{ + changed = $false + ping = $data +}; echo $result | ConvertTo-Json; diff --git a/library/windows/win_stat.ps1 b/library/windows/win_stat.ps1 index 50a46064d2..d7b754a283 100644 --- a/library/windows/win_stat.ps1 +++ b/library/windows/win_stat.ps1 @@ -28,36 +28,37 @@ If ($params.path.GetType) $get_md5 = $TRUE; If ($params.get_md5.GetType) { - $get_md5 = $params.get_md5; + $get_md5 = $params.get_md5; } -$stat = New-Object psobject; +$result = New-Object psobject @{ + stat = New-Object psobject + changed = $false +}; + If (Test-Path $path) { - Set-Attr $stat "exists" $TRUE; + Set-Attr $result.stat "exists" $TRUE; $info = Get-Item $path; If ($info.Directory) # Only files have the .Directory attribute. { - Set-Attr $stat "isdir" $FALSE; - Set-Attr $stat "size" $info.Length; + Set-Attr $result.stat "isdir" $FALSE; + Set-Attr $result.stat "size" $info.Length; } Else { - Set-Attr $stat "isdir" $TRUE; + Set-Attr $result.stat "isdir" $TRUE; } } Else { - Set-Attr $stat "exists" $FALSE; + Set-Attr $result.stat "exists" $FALSE; } -If ($get_md5 -and $stat.exists -and -not $stat.isdir) +If ($get_md5 -and $result.stat.exists -and -not $result.stat.isdir) { $path_md5 = (Get-FileHash -Path $path -Algorithm MD5).Hash.ToLower(); - Set-Attr $stat "md5" $path_md5; + Set-Attr $result.stat "md5" $path_md5; } -$result = New-Object psobject; -Set-Attr $result "stat" $stat; -Set-Attr $result "changed" $FALSE; echo $result | ConvertTo-Json; From 74c43c94cfe44a74d1e445d20a49c27065fea1a4 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Wed, 18 Jun 2014 11:18:44 -0500 Subject: [PATCH 031/107] Allow specifying remote powershell version via environment variable. --- lib/ansible/runner/shell_plugins/powershell.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ansible/runner/shell_plugins/powershell.py b/lib/ansible/runner/shell_plugins/powershell.py index 031077215e..eead8d50b1 100644 --- a/lib/ansible/runner/shell_plugins/powershell.py +++ b/lib/ansible/runner/shell_plugins/powershell.py @@ -24,6 +24,12 @@ import time _common_args = ['PowerShell', '-NoProfile', '-NonInteractive'] +# Primarily for testing, allow explicitly specifying PowerShell version via +# an environment variable. +_powershell_version = os.environ.get('POWERSHELL_VERSION', None) +if _powershell_version: + _common_args = ['PowerShell', '-Version', _powershell_version] + _common_args[1:] + def _escape(value, include_vars=False): '''Return value escaped for use in PowerShell command.''' # http://www.techotopia.com/index.php/Windows_PowerShell_1.0_String_Quoting_and_Escape_Sequences From 97f4f56286fd7b5de27d1e5c01a15e4a1dcc8a6a Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 11:48:50 -0500 Subject: [PATCH 032/107] Add Exit-Json and Fail-Json powershell helper functions --- lib/ansible/module_utils/powershell.ps1 | 17 +++++++++++++++++ library/windows/setup.ps1 | 2 +- library/windows/slurp.ps1 | 2 +- library/windows/win_ping.ps1 | 2 +- library/windows/win_stat.ps1 | 2 +- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index 54d2eedc4f..8c05706b60 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -47,3 +47,20 @@ Function Set-Attr($obj, $name, $value) $obj | Add-Member -Force -MemberType NoteProperty -Name $name -Value $value } +# Helper function to convert a powershell object to JSON to echo it, exiting +# the script +Function Exit-Json($obj) +{ + echo $obj | ConvertTo-Json + Exit +} + +# Helper function to add the "msg" property and "failed" property, convert the +# powershell object to JSON and echo it, exiting the script +Function Fail-Json($obj, $message) +{ + Set-Attr $obj "msg" $message + Set-Attr $obj "failed" $true + echo $obj | ConvertTo-Json + Exit +} diff --git a/library/windows/setup.ps1 b/library/windows/setup.ps1 index 883c279205..07d32c56f5 100644 --- a/library/windows/setup.ps1 +++ b/library/windows/setup.ps1 @@ -34,4 +34,4 @@ Set-Attr $result.ansible_facts "ansible_os_family" "Windows" Set-Attr $result.ansible_facts "ansible_distribution" $osversion.VersionString Set-Attr $result.ansible_facts "ansible_distribution_version" $osversion.Version.ToString() -echo $result | ConvertTo-Json; +Exit-Json $result; diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 index d224539ee6..40bb5eba03 100644 --- a/library/windows/slurp.ps1 +++ b/library/windows/slurp.ps1 @@ -44,4 +44,4 @@ $result = New-Object psobject @{ encoding = "base64" }; Set-Attr $result "content" $content; -echo $result | ConvertTo-Json; +Exit-Json $result; diff --git a/library/windows/win_ping.ps1 b/library/windows/win_ping.ps1 index 0771ea3125..ec6d530025 100644 --- a/library/windows/win_ping.ps1 +++ b/library/windows/win_ping.ps1 @@ -29,4 +29,4 @@ $result = New-Object psobject @{ changed = $false ping = $data }; -echo $result | ConvertTo-Json; +Exit-Json $result; diff --git a/library/windows/win_stat.ps1 b/library/windows/win_stat.ps1 index d7b754a283..fa5596ec1b 100644 --- a/library/windows/win_stat.ps1 +++ b/library/windows/win_stat.ps1 @@ -61,4 +61,4 @@ If ($get_md5 -and $result.stat.exists -and -not $result.stat.isdir) Set-Attr $result.stat "md5" $path_md5; } -echo $result | ConvertTo-Json; +Exit-Json $result; From fab0374572c5450bfb145017cce4372f23462345 Mon Sep 17 00:00:00 2001 From: Craig Ackerman Date: Wed, 18 Jun 2014 16:48:52 +0000 Subject: [PATCH 033/107] Added Windows intro content --- docsite/rst/intro_windows.rst | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index aa6d51e0bb..af1e25a427 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -106,11 +106,38 @@ Additional modules may be submitted as pull requests to github. .. _windows_system_prep: System Prep -``````````` +`````````` + +In order for Ansible to manage your windows machines, you will have to enable Powershell remoting first, which also enables WinRM:: + +From the Windows host, launch the Powershell Client. For information on Powershell, visit 'Microsoft's Using Powershell article ' + +In the powershell session, run the following to enable PS Remoting and set the execution policy + +.. code-block:: bash + + $ Enable-PSRemoting -Force + $ Set-ExecutionPolicy RemoteSigned + +If your Windows firewall is enabled, you must also run the following command to allow firewall access to the public firewall profile:: + + .. code-block:: bash + + $ Windows 2012 / 2012R2 + $ Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" -RemoteAddress Any + $ Windows 2008 / 2008R2 + $ NetSH ADVFirewall Set AllProfiles Settings remotemanagement Enable + +Best Practices +By default, Powershell remoting enables an HTTP listener. The following commands enable an HTTPS listener, which secures communication between the Control Machine and windows. + +.. code-block:: bash + $ Delete the http listener + $ WinRM delete winrm/config/listener?Address=*+Transport=HTTP + $ Create the https listener + $ Insert code here -In order for Ansible to manage your windows machines you will have to enable powershell remoting first:: - How to do it goes here .. _windows_and_linux_control_machine: From aba2cbfbc6b693283eddda6abae2fa7add2b9152 Mon Sep 17 00:00:00 2001 From: Craig Ackerman Date: Wed, 18 Jun 2014 19:28:11 +0000 Subject: [PATCH 034/107] Additional Windows Setup info --- docsite/rst/intro_windows.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index af1e25a427..48781bdc24 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -131,13 +131,18 @@ If your Windows firewall is enabled, you must also run the following command to Best Practices By default, Powershell remoting enables an HTTP listener. The following commands enable an HTTPS listener, which secures communication between the Control Machine and windows. +An SSL certificate for server authentication is required to create the HTTPS listener. The existence of an existing certificate in the computer account can be verified by using the MMC snap-in, as documented ' + +A best practice for SSL certificates is generating them from an internal or external certificate authority. An existing certificate could be located in the computer account certificate store 'using the following article '. + +Alternatively, a self-signed SSL certificate can be generated in powershell using 'the following technet article '. At a minimum, the subject name should match the hostname, and Server Authentication is required. Once the self signed certificate is obtained, the certificate thumbprint can be identified using 'How to: Retrieve the Thumbprint of a Certificate ' + .. code-block:: bash + + $ Create the https listener + $ winrm create winrm/config/Listener?Address=*+Transport=HTTPS  @{Hostname="host_name";CertificateThumbprint="certificate_thumbprint"} $ Delete the http listener $ WinRM delete winrm/config/listener?Address=*+Transport=HTTP - $ Create the https listener - $ Insert code here - - .. _windows_and_linux_control_machine: From eb33e3e2ef159b5e2a275d98031a601acf47c3d1 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 11:53:18 -0500 Subject: [PATCH 035/107] Slight tweak to windows wording. --- library/system/setup | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/system/setup b/library/system/setup index c5b408afca..3194500cb2 100644 --- a/library/system/setup +++ b/library/system/setup @@ -55,7 +55,8 @@ notes: remote systems. (See also M(facter) and M(ohai).) - The filter option filters only the first level subkey below ansible_facts. - If the target host is Windows, you will not currently have the ability to use - C(fact_path) or C(filter). + C(fact_path) or C(filter) as this is provided by a simpler implementation of the module. + Different facts are returned for Windows hosts. author: Michael DeHaan ''' From f423a8dffe386c6a00021b5310997bf5b6832dc7 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 12:11:13 -0500 Subject: [PATCH 036/107] Slight examples tweak --- docsite/rst/intro_windows.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 48781bdc24..de7f3d5d91 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -106,7 +106,7 @@ Additional modules may be submitted as pull requests to github. .. _windows_system_prep: System Prep -`````````` +``````````` In order for Ansible to manage your windows machines, you will have to enable Powershell remoting first, which also enables WinRM:: @@ -123,9 +123,10 @@ If your Windows firewall is enabled, you must also run the following command to .. code-block:: bash - $ Windows 2012 / 2012R2 + # Windows 2012 / 2012R2 $ Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" -RemoteAddress Any - $ Windows 2008 / 2008R2 + + # Windows 2008 / 2008R2 $ NetSH ADVFirewall Set AllProfiles Settings remotemanagement Enable Best Practices From 003448defc2f6b9974c68788c3d91a9707866d36 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Wed, 18 Jun 2014 12:29:14 -0500 Subject: [PATCH 037/107] Add memory and IP information to setup --- library/windows/setup.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/windows/setup.ps1 b/library/windows/setup.ps1 index 07d32c56f5..adec2d9ae0 100644 --- a/library/windows/setup.ps1 +++ b/library/windows/setup.ps1 @@ -26,6 +26,8 @@ $result = New-Object psobject @{ }; $osversion = [Environment]::OSVersion +$memory = Get-WmiObject win32_Pysicalmemory +$netcfg = Get-WmiObject win32_NetworkAdapterConfiguration Set-Attr $result.ansible_facts "ansible_hostname" $env:COMPUTERNAME; Set-Attr $result.ansible_facts "ansible_fqdn" "$([System.Net.Dns]::GetHostByName((hostname)).HostName)" @@ -34,4 +36,10 @@ Set-Attr $result.ansible_facts "ansible_os_family" "Windows" Set-Attr $result.ansible_facts "ansible_distribution" $osversion.VersionString Set-Attr $result.ansible_facts "ansible_distribution_version" $osversion.Version.ToString() +Set-Attr $result.ansible_facts "ansible_totalmem" $memory.Capacity.ToString() + +$ips = @() +Foreach ($ip in $netcfg.IPAddress) { If ($ip) { $ips += $ip } } +Set-Attr $result.ansible_facts "ansible_ip_addresses" $ips + Exit-Json $result; From 13a732e6b9ed913fb4828bf1ce41b324fb29b87f Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 13:14:00 -0500 Subject: [PATCH 038/107] First pass at win_msi.ps1 msi installer module --- library/windows/win_msi.ps1 | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 library/windows/win_msi.ps1 diff --git a/library/windows/win_msi.ps1 b/library/windows/win_msi.ps1 new file mode 100644 index 0000000000..6277035208 --- /dev/null +++ b/library/windows/win_msi.ps1 @@ -0,0 +1,62 @@ +#!powershell +# 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 . + +# WANT_JSON +# POWERSHELL_COMMON + +$params = Parse-Args $args; + +$result = New-Object psobject; +Set-Attr $result "changed" $false; + +If (-not $params.path.GetType) +{ + Fail-Json $result "missing required arguments: path" +} + +$extra_args = "" +If ($params.extra_args.GetType) +{ + $extra_args = $params.extra_args; +} + +If ($params.creates.GetType) +{ + If (Test-File $creates) + { + Exit-Json $result; + } +} + +$logfile = [IO.Path]::GetTempFileName(); +$stdoutfile = [IO.Path]::GetTempFileName(); +$stderrfile = [IO.Path]::GetTempFileName(); +msiexec.exe /i $params.path /qb /l $logfile $extra_args; + +Set-Attr $result "changed" $true; + +$logcontents = Get-Content $logfile; +Remove-Item $logfile; +$stdoutcontents = Get-Content $stdoutfile; +Remove-Item $stdoutfile; +$stderrcontents = Get-Content $stderrfile; +Remove-Item $stderrfile; + +Set-Attr $result "log" $logcontents; +Set-Attr $result "stdout" $stdoutcontents; +Set-Attr $result "stderr" $stderrcontents; + +Exit-Json $result; From a8ca57976163d303fe81b36a8ab481cff1c2cbd9 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Wed, 18 Jun 2014 11:49:43 -0500 Subject: [PATCH 039/107] Fix host parameter to vvvvv. --- lib/ansible/runner/connection_plugins/winrm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/runner/connection_plugins/winrm.py b/lib/ansible/runner/connection_plugins/winrm.py index 5bf0467f7d..d9107995e5 100644 --- a/lib/ansible/runner/connection_plugins/winrm.py +++ b/lib/ansible/runner/connection_plugins/winrm.py @@ -42,7 +42,7 @@ _winrm_cache = { } def vvvvv(msg, host=None): - verbose(msg, host=None, caplevel=4) + verbose(msg, host=host, caplevel=4) class Connection(object): '''WinRM connections over HTTP/HTTPS.''' From 5d7f67569fab3e5805fe8076c3f2edf1895bb26d Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 13:26:33 -0500 Subject: [PATCH 040/107] At state for win_msi and add documentation --- library/windows/win_msi | 53 +++++++++++++++++++++++++++++++++++++ library/windows/win_msi.ps1 | 11 +++++++- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 library/windows/win_msi diff --git a/library/windows/win_msi b/library/windows/win_msi new file mode 100644 index 0000000000..894d7e2fc7 --- /dev/null +++ b/library/windows/win_msi @@ -0,0 +1,53 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Matt Martz , and others +# +# 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 . + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +DOCUMENTATION = ''' +--- +module: win_msi +version_added: "1.7" +short_description: Installs and uninstalls Windows MSI files +description: + - Installs or uninstalls a Windows MSI file that is already located on the target server +options: + path: + description: + - File system path to the MSI file to install + required: true + state: + description: + - Whether the MSI file should be installed or uninstalled + choices: + - present + - absent + default: present +author: Matt Martz +''' + +EXAMPLES = ''' +# Install an MSI file +- win_msi: path=C:\\\\7z920-x64.msi + +# Uninstall an MSI file +- win_msi: path=C:\\\\7z920-x64.msi state=absent +''' + diff --git a/library/windows/win_msi.ps1 b/library/windows/win_msi.ps1 index 6277035208..d28ae85bd7 100644 --- a/library/windows/win_msi.ps1 +++ b/library/windows/win_msi.ps1 @@ -1,4 +1,6 @@ #!powershell +# (c) 2014, Matt Martz , and others +# # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -44,7 +46,14 @@ If ($params.creates.GetType) $logfile = [IO.Path]::GetTempFileName(); $stdoutfile = [IO.Path]::GetTempFileName(); $stderrfile = [IO.Path]::GetTempFileName(); -msiexec.exe /i $params.path /qb /l $logfile $extra_args; +if ($params.state.GetType -and $params.state -eq "absent") +{ + msiexec.exe /x $params.path /qb /l $logfile $extra_args; +} +Else +{ + msiexec.exe /i $params.path /qb /l $logfile $extra_args; +} Set-Attr $result "changed" $true; From 069fa71d873409ba97506465605f312b45557894 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 18 Jun 2014 13:32:58 -0500 Subject: [PATCH 041/107] Remove unneeded code, document creates param and ensure creates only affects state=present --- library/windows/win_msi | 7 ++++++- library/windows/win_msi.ps1 | 10 +--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/library/windows/win_msi b/library/windows/win_msi index 894d7e2fc7..9eb6f1bafa 100644 --- a/library/windows/win_msi +++ b/library/windows/win_msi @@ -27,7 +27,8 @@ module: win_msi version_added: "1.7" short_description: Installs and uninstalls Windows MSI files description: - - Installs or uninstalls a Windows MSI file that is already located on the target server + - Installs or uninstalls a Windows MSI file that is already located on the + target server options: path: description: @@ -40,6 +41,10 @@ options: - present - absent default: present + creates: + description: + - Path to a file created by installing the MSI to prevent from + attempting to reinstall the package on every run author: Matt Martz ''' diff --git a/library/windows/win_msi.ps1 b/library/windows/win_msi.ps1 index d28ae85bd7..1c2bc8a301 100644 --- a/library/windows/win_msi.ps1 +++ b/library/windows/win_msi.ps1 @@ -35,7 +35,7 @@ If ($params.extra_args.GetType) $extra_args = $params.extra_args; } -If ($params.creates.GetType) +If ($params.creates.GetType -and $params.state.GetType -and $params.state -ne "absent") { If (Test-File $creates) { @@ -44,8 +44,6 @@ If ($params.creates.GetType) } $logfile = [IO.Path]::GetTempFileName(); -$stdoutfile = [IO.Path]::GetTempFileName(); -$stderrfile = [IO.Path]::GetTempFileName(); if ($params.state.GetType -and $params.state -eq "absent") { msiexec.exe /x $params.path /qb /l $logfile $extra_args; @@ -59,13 +57,7 @@ Set-Attr $result "changed" $true; $logcontents = Get-Content $logfile; Remove-Item $logfile; -$stdoutcontents = Get-Content $stdoutfile; -Remove-Item $stdoutfile; -$stderrcontents = Get-Content $stderrfile; -Remove-Item $stderrfile; Set-Attr $result "log" $logcontents; -Set-Attr $result "stdout" $stdoutcontents; -Set-Attr $result "stderr" $stderrcontents; Exit-Json $result; From 5b15194a0d20cc5d28601e22e856fb7ad7bb0f93 Mon Sep 17 00:00:00 2001 From: Don Schenck Date: Wed, 18 Jun 2014 14:34:57 -0500 Subject: [PATCH 042/107] PowerShell script to assure PowerShell 3 is installed Will install PowerShell 3 if the machine has a lower version. WILL NOT do anything if PowerShell 3 (or higher) is already installed. --- examples/scripts/UpgradeToPS3.ps1 | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 examples/scripts/UpgradeToPS3.ps1 diff --git a/examples/scripts/UpgradeToPS3.ps1 b/examples/scripts/UpgradeToPS3.ps1 new file mode 100644 index 0000000000..5fe5dd5195 --- /dev/null +++ b/examples/scripts/UpgradeToPS3.ps1 @@ -0,0 +1,71 @@ +# Upgrade to PowerShell 3.0 + +# Get version of OS + +# 6.0 is 2008 +# 6.1 is 2008 R2 +# 6.2 is 2012 +# 6.3 is 2012 R2 + + +if ($PSVersionTable.psversion.Major -ge 3) +{ + write-host "Powershell 3 Installed already; You don't need this" + Exit +} + +$powershellpath = "C:\powershell" + +function download-file +{ + param ([string]$path, [string]$local) + $client = new-object system.net.WebClient + $client.Headers.Add("user-agent", "PowerShell") + $client.downloadfile($path, $local) +} + +if (!(test-path $powershellpath)) +{ + New-Item -ItemType directory -Path $powershellpath +} + + +# .NET Framework 4.0 is necessary. + +if (($PSVersionTable.CLRVersion.Major) -lt 4) +{ + $DownloadUrl = "http://download.microsoft.com/download/B/A/4/BA4A7E71-2906-4B2D-A0E1-80CF16844F5F/dotNetFx45_Full_x86_x64.exe" + $FileName = $DownLoadUrl.Split('/')[-1] + download-file $downloadurl "$powershellpath\$filename" + ."$powershellpath\$filename" /quiet /norestart +} + +#You may need to reboot after the .NET install if so just run the script again. + +# If the Operating System is above 6.2, then you already have PowerShell Version > 3 +if ([Environment]::OSVersion.Version.Major -gt 6) +{ + Exit +} + + +$osminor = [environment]::OSVersion.Version.Minor + +if ($osminor -eq 1) +{ + $DownloadUrl = "http://download.microsoft.com/download/3/D/6/3D61D262-8549-4769-A660-230B67E15B25/Windows6.1-KB2819745-x86-MultiPkg.msu" +} +elseif ($osminor -eq 0) +{ + $DownloadUrl = "http://download.microsoft.com/download/E/7/6/E76850B8-DA6E-4FF5-8CCE-A24FC513FD16/Windows6.0-KB2506146-x64.msu" +} +else +{ + # Nothing to do; In theory this point will never be reached. + Exit +} + +$FileName = $DownLoadUrl.Split('/')[-1] +download-file $downloadurl "$powershellpath\$filename" + +."$powershellpath\$filename" /quiet \ No newline at end of file From 17f54afa08845f5164c63283cc371d8e63a3f4c9 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Wed, 18 Jun 2014 15:04:26 -0500 Subject: [PATCH 043/107] Add win_get_url module --- library/windows/win_get_url.ps1 | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100755 library/windows/win_get_url.ps1 diff --git a/library/windows/win_get_url.ps1 b/library/windows/win_get_url.ps1 new file mode 100755 index 0000000000..b555cc7a52 --- /dev/null +++ b/library/windows/win_get_url.ps1 @@ -0,0 +1,56 @@ +#!powershell +# This file is part of Ansible. +# +# Copyright 2014, Paul Durivage +# +# 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 . + +# WANT_JSON +# POWERSHELL_COMMON + +$params = Parse-Args $args; + +$result = New-Object psobject @{ + win_get_url = New-Object psobject + changed = $false +} + +If ($params.url) { + $url = $params.url +} +Else { + Fail-Json $result "mising required argument: url" +} + +If ($params.dest) { + $dest = $params.dest +} +Else { + Fail-Json $result "missing required argument: dest" +} + +$client = New-Object System.Net.WebClient + +Try { + $client.DownloadFile($url, $dest) + $result.changed = $true +} +Catch { + Fail-Json $result "Error downloading $url to $dest" +} + +Set-Attr $result.win_get_url "url" $url +Set-Attr $result.win_get_url "dest" $dest + +Exit-Json $result; From b22e2160cf602f261ef9cd00f5a35da351df1456 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Wed, 18 Jun 2014 15:04:51 -0500 Subject: [PATCH 044/107] Add win_get_url module documentation --- library/windows/win_get_url | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 library/windows/win_get_url diff --git a/library/windows/win_get_url b/library/windows/win_get_url new file mode 100644 index 0000000000..540b6b61b6 --- /dev/null +++ b/library/windows/win_get_url @@ -0,0 +1,34 @@ +DOCUMENTATION = ''' +--- +module: win_get_url +version_added: "1.7" +short_description: Fetches a file from a given URL +description: + - Fetches a file from a URL and saves to locally +options: + url: + description: + - The full URL of a file to download + required: true + default: null + aliases: [] + dest: + description: + - The absolute path of the location to save the file at the URL. Be sure to include a filename and extension as appropriate. + required: false + default: yes + aliases: [] +author: Paul Durivage +''' + +EXAMPLES = ''' +# Downloading a JPEG and saving it to a file with the ansible command. +# Note the "dest" is quoted rather instead of escaping the backslashes +$ ansible -i hosts -c winrm -m win_get_url -a "url=http://www.example.com/earthrise.jpg dest='C:\Users\Administrator\earthrise.jpg'" all + +# Playbook example +- name: Download earthrise.jpg to 'C:\Users\RandomUser\earthrise.jpg' + win_get_url: + url: 'http://www.example.com/earthrise.jpg' + dest: 'C:\Users\RandomUser\earthrise.jpg' +''' \ No newline at end of file From dfd4f1809539278e71c52674f177ace083fe28fe Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 15:21:52 -0500 Subject: [PATCH 045/107] Docsite things. --- docsite/rst/intro.rst | 2 +- docsite/rst/intro_windows.rst | 16 ++++++++-------- docsite/rst/playbooks_variables.rst | 9 ++++----- hacking/module_formatter.py | 6 ++++++ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/docsite/rst/intro.rst b/docsite/rst/intro.rst index 20518529e5..9b30a18bbb 100644 --- a/docsite/rst/intro.rst +++ b/docsite/rst/intro.rst @@ -13,5 +13,5 @@ Before we dive into the really fun parts -- playbooks, configuration management, intro_patterns intro_adhoc intro_configuration - + intro_windows diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index de7f3d5d91..0319390c74 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -67,8 +67,7 @@ Browse this index to see what is available. In many cases, it may not be neccessary to even write or use an Ansible module. -In particular, the "win_script" module can be used to run arbitrary powershell scripts, allowing Windows administrators familiar with powershell a very -native way to do things, as in the following playbook: +In particular, the "win_script" module can be used to run arbitrary powershell scripts, allowing Windows administrators familiar with powershell a very native way to do things, as in the following playbook:: - hosts: windows tasks: @@ -108,9 +107,9 @@ Additional modules may be submitted as pull requests to github. System Prep ``````````` -In order for Ansible to manage your windows machines, you will have to enable Powershell remoting first, which also enables WinRM:: +In order for Ansible to manage your windows machines, you will have to enable Powershell remoting first, which also enables WinRM. -From the Windows host, launch the Powershell Client. For information on Powershell, visit 'Microsoft's Using Powershell article ' +From the Windows host, launch the Powershell Client. For information on Powershell, visit `Microsoft's Using Powershell article `_. In the powershell session, run the following to enable PS Remoting and set the execution policy @@ -134,9 +133,9 @@ By default, Powershell remoting enables an HTTP listener. The following commands An SSL certificate for server authentication is required to create the HTTPS listener. The existence of an existing certificate in the computer account can be verified by using the MMC snap-in, as documented ' -A best practice for SSL certificates is generating them from an internal or external certificate authority. An existing certificate could be located in the computer account certificate store 'using the following article '. +A best practice for SSL certificates is generating them from an internal or external certificate authority. An existing certificate could be located in the computer account certificate store `using the following article `_. -Alternatively, a self-signed SSL certificate can be generated in powershell using 'the following technet article '. At a minimum, the subject name should match the hostname, and Server Authentication is required. Once the self signed certificate is obtained, the certificate thumbprint can be identified using 'How to: Retrieve the Thumbprint of a Certificate ' +Alternatively, a self-signed SSL certificate can be generated in powershell using 'the following technet article '. At a minimum, the subject name should match the hostname, and Server Authentication is required. Once the self signed certificate is obtained, the certificate thumbprint can be identified using `How to: Retrieve the Thumbprint of a Certificate `_ .. code-block:: bash @@ -192,7 +191,7 @@ Running individual commands uses the 'raw' module, as opposed to the shell or co register: ipconfig - debug: var=ipconfig -And for a final example, here's how to use the win_stat module to test for file existance. Note that the data returned byt he win_stat module is slightly different than what is provided by the Linux equivalent. +And for a final example, here's how to use the win_stat module to test for file existance. Note that the data returned byt he win_stat module is slightly different than what is provided by the Linux equivalent:: - name: test stat module hosts: windows @@ -211,7 +210,7 @@ And for a final example, here's how to use the win_stat module to test for file - "stat_file.stat.size > 0" - "stat_file.stat.md5" -Again, recall that the Windows modules are all listed in the Windows category of modules, with the exception that the "raw", "script", and "fetch" modules are also available. These modules do not start with a "win_" prefix. +Again, recall that the Windows modules are all listed in the Windows category of modules, with the exception that the "raw", "script", and "fetch" modules are also available. These modules do not start with a "win" prefix. .. _windows_contributions: @@ -232,3 +231,4 @@ form of new modules, tweaks to existing modules, documentation, or something els `irc.freenode.net `_ #ansible IRC chat channel + diff --git a/docsite/rst/playbooks_variables.rst b/docsite/rst/playbooks_variables.rst index 8900948ae8..763e22d701 100644 --- a/docsite/rst/playbooks_variables.rst +++ b/docsite/rst/playbooks_variables.rst @@ -218,12 +218,11 @@ Version Comparison Filters To compare a version number, such as checking if the ``ansible_distribution_version`` version is greater than or equal to '12.04', you can use the ``version_compare`` filter:: -The ``version_compare`` filter can also be used to evaluate the ``ansible_distribution_version``:: +The `version_compare` filter can also be used to evaluate the `ansible_distribution_version`:: {{ ansible_distribution_version | version_compare('12.04', '>=') }} -If ``ansible_distribution_version`` is greater than or equal to 12, this filter will return True, otherwise -it will return False. +If ``ansible_distribution_version`` is greater than or equal to 12, this filter will return True, otherwise it will return False. The ``version_compare`` filter accepts the following operators:: @@ -234,10 +233,10 @@ be used. The default is ``False``, and if set as ``True`` will use more strict {{ sample_version_var | version_compare('1.0', operator='lt', strict=True) }} -.. _random_filter +.. _random_filter: Random Number Filter --------------------------- +-------------------- .. versionadded:: 1.6 diff --git a/hacking/module_formatter.py b/hacking/module_formatter.py index 0a36c3951c..f74d09ad72 100755 --- a/hacking/module_formatter.py +++ b/hacking/module_formatter.py @@ -123,6 +123,12 @@ def list_modules(module_dir): if os.path.isdir(d): files2 = glob.glob("%s/*" % d) for f in files2: + + if f.endswith(".ps1"): + # windows powershell modules have documentation stubs in python docstring + # format (they are not executed) so skip the ps1 format files + continue + tokens = f.split("/") module = tokens[-1] category = tokens[-2] From 5de34fca223a42dff420758695f1c6bbe3143e13 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Wed, 18 Jun 2014 15:23:20 -0500 Subject: [PATCH 046/107] Make non-executable --- library/windows/win_get_url.ps1 | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 library/windows/win_get_url.ps1 diff --git a/library/windows/win_get_url.ps1 b/library/windows/win_get_url.ps1 old mode 100755 new mode 100644 From b0648fda0b0c8305c1d69dd6d00b028531f1ca1d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 15:27:14 -0500 Subject: [PATCH 047/107] Docsite formatting fix. --- docsite/rst/playbooks_variables.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docsite/rst/playbooks_variables.rst b/docsite/rst/playbooks_variables.rst index 763e22d701..d52914af1b 100644 --- a/docsite/rst/playbooks_variables.rst +++ b/docsite/rst/playbooks_variables.rst @@ -216,9 +216,9 @@ Version Comparison Filters .. versionadded:: 1.6 To compare a version number, such as checking if the ``ansible_distribution_version`` -version is greater than or equal to '12.04', you can use the ``version_compare`` filter:: +version is greater than or equal to '12.04', you can use the ``version_compare`` filter. -The `version_compare` filter can also be used to evaluate the `ansible_distribution_version`:: +The ``version_compare`` filter can also be used to evaluate the ``ansible_distribution_version``:: {{ ansible_distribution_version | version_compare('12.04', '>=') }} From ef968efa8ba77905f4db54616f030f9ab9916f96 Mon Sep 17 00:00:00 2001 From: Don Schenck Date: Wed, 18 Jun 2014 21:40:14 +0000 Subject: [PATCH 048/107] Fixed bugs related to .NET Framework version. Version 3.5 or higher is now assumed. --- examples/scripts/UpgradeToPS3.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/scripts/UpgradeToPS3.ps1 b/examples/scripts/UpgradeToPS3.ps1 index 5fe5dd5195..f6a303c951 100644 --- a/examples/scripts/UpgradeToPS3.ps1 +++ b/examples/scripts/UpgradeToPS3.ps1 @@ -32,13 +32,13 @@ if (!(test-path $powershellpath)) # .NET Framework 4.0 is necessary. -if (($PSVersionTable.CLRVersion.Major) -lt 4) -{ - $DownloadUrl = "http://download.microsoft.com/download/B/A/4/BA4A7E71-2906-4B2D-A0E1-80CF16844F5F/dotNetFx45_Full_x86_x64.exe" - $FileName = $DownLoadUrl.Split('/')[-1] - download-file $downloadurl "$powershellpath\$filename" - ."$powershellpath\$filename" /quiet /norestart -} +#if (($PSVersionTable.CLRVersion.Major) -lt 2) +#{ +# $DownloadUrl = "http://download.microsoft.com/download/B/A/4/BA4A7E71-2906-4B2D-A0E1-80CF16844F5F/dotNetFx45_Full_x86_x64.exe" +# $FileName = $DownLoadUrl.Split('/')[-1] +# download-file $downloadurl "$powershellpath\$filename" +# ."$powershellpath\$filename" /quiet /norestart +#} #You may need to reboot after the .NET install if so just run the script again. @@ -53,7 +53,7 @@ $osminor = [environment]::OSVersion.Version.Minor if ($osminor -eq 1) { - $DownloadUrl = "http://download.microsoft.com/download/3/D/6/3D61D262-8549-4769-A660-230B67E15B25/Windows6.1-KB2819745-x86-MultiPkg.msu" + $DownloadUrl = "http://download.microsoft.com/download/E/7/6/E76850B8-DA6E-4FF5-8CCE-A24FC513FD16/Windows6.1-KB2506143-x64.msu" } elseif ($osminor -eq 0) { From 243cd877ae9f46daeaaf62d95d096b45c91ba10f Mon Sep 17 00:00:00 2001 From: Chris Church Date: Wed, 18 Jun 2014 16:10:10 -0500 Subject: [PATCH 049/107] Add integration tests for fetch/slurp, make powershell fetch/slurp work as close as possible to existing fetch/slurp modules. --- lib/ansible/module_utils/powershell.ps1 | 2 +- lib/ansible/runner/action_plugins/fetch.py | 11 +- .../runner/connection_plugins/winrm.py | 64 +++++-- .../runner/shell_plugins/powershell.py | 16 +- lib/ansible/utils/__init__.py | 4 +- library/windows/slurp.ps1 | 45 +++-- .../roles/test_win_fetch/tasks/main.yml | 168 ++++++++++++++++++ .../roles/test_win_ping/tasks/main.yml | 18 +- .../roles/test_win_raw/tasks/main.yml | 18 +- .../files/test_script_with_errors.ps1 | 4 +- .../roles/test_win_script/tasks/main.yml | 18 +- .../roles/test_win_slurp/tasks/main.yml | 77 ++++++++ test/integration/test_winrm.yml | 2 + 13 files changed, 403 insertions(+), 44 deletions(-) create mode 100644 test/integration/roles/test_win_fetch/tasks/main.yml create mode 100644 test/integration/roles/test_win_slurp/tasks/main.yml diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index 8c05706b60..a911ea95dd 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -62,5 +62,5 @@ Function Fail-Json($obj, $message) Set-Attr $obj "msg" $message Set-Attr $obj "failed" $true echo $obj | ConvertTo-Json - Exit + Exit 1 } diff --git a/lib/ansible/runner/action_plugins/fetch.py b/lib/ansible/runner/action_plugins/fetch.py index 1600e8803c..00622f1282 100644 --- a/lib/ansible/runner/action_plugins/fetch.py +++ b/lib/ansible/runner/action_plugins/fetch.py @@ -57,19 +57,24 @@ class ActionModule(object): return ReturnData(conn=conn, result=results) source = os.path.expanduser(source) + source = conn.shell.join_path(source) + if os.path.sep not in conn.shell.join_path('a', ''): + source_local = source.replace('\\', '/') + else: + source_local = source if flat: - if dest.endswith("/"): # CCTODO: Fix path for Windows hosts. + if dest.endswith("/"): # if the path ends with "/", we'll use the source filename as the # destination filename - base = os.path.basename(source) + base = os.path.basename(source_local) dest = os.path.join(dest, base) if not dest.startswith("/"): # if dest does not start with "/", we'll assume a relative path dest = utils.path_dwim(self.runner.basedir, dest) else: # files are saved in dest dir, with a subdir for each host, then the filename - dest = "%s/%s/%s" % (utils.path_dwim(self.runner.basedir, dest), conn.host, source) + dest = "%s/%s/%s" % (utils.path_dwim(self.runner.basedir, dest), conn.host, source_local) dest = os.path.expanduser(dest.replace("//","/")) diff --git a/lib/ansible/runner/connection_plugins/winrm.py b/lib/ansible/runner/connection_plugins/winrm.py index d9107995e5..b9c1f3be8e 100644 --- a/lib/ansible/runner/connection_plugins/winrm.py +++ b/lib/ansible/runner/connection_plugins/winrm.py @@ -178,8 +178,8 @@ class Connection(object): cmd_parts = powershell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) if result.status_code != 0: - raise RuntimeError(result.std_err.encode('utf-8')) - except Exception: # IOError? + raise IOError(result.std_err.encode('utf-8')) + except Exception: traceback.print_exc() raise errors.AnsibleError("failed to transfer file to %s" % out_path) @@ -189,31 +189,61 @@ class Connection(object): buffer_size = 2**20 # 1MB chunks if not os.path.exists(os.path.dirname(out_path)): os.makedirs(os.path.dirname(out_path)) - with open(out_path, 'wb') as out_file: + out_file = None + try: offset = 0 while True: try: script = ''' - $bufferSize = %d; - $stream = [System.IO.File]::OpenRead("%s"); - $stream.Seek(%d, [System.IO.SeekOrigin]::Begin) | Out-Null; - $buffer = New-Object Byte[] $bufferSize; - $bytesRead = $stream.Read($buffer, 0, $bufferSize); - $bytes = $buffer[0..($bytesRead-1)]; - [System.Convert]::ToBase64String($bytes); - $stream.Close() | Out-Null; - ''' % (buffer_size, powershell._escape(in_path), offset) + If (Test-Path -PathType Leaf "%(path)s") + { + $stream = [System.IO.File]::OpenRead("%(path)s"); + $stream.Seek(%(offset)d, [System.IO.SeekOrigin]::Begin) | Out-Null; + $buffer = New-Object Byte[] %(buffer_size)d; + $bytesRead = $stream.Read($buffer, 0, %(buffer_size)d); + $bytes = $buffer[0..($bytesRead-1)]; + [System.Convert]::ToBase64String($bytes); + $stream.Close() | Out-Null; + } + ElseIf (Test-Path -PathType Container "%(path)s") + { + Write-Host "[DIR]"; + } + Else + { + Write-Error "%(path)s does not exist"; + Exit 1; + } + ''' % dict(buffer_size=buffer_size, path=powershell._escape(in_path), offset=offset) vvvv("WINRM FETCH %s to %s (offset=%d)" % (in_path, out_path, offset), host=self.host) cmd_parts = powershell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) - data = base64.b64decode(result.std_out.strip()) - out_file.write(data) - if len(data) < buffer_size: + if result.status_code != 0: + raise IOError(result.std_err.encode('utf-8')) + if result.std_out.strip() == '[DIR]': + data = None + else: + data = base64.b64decode(result.std_out.strip()) + if data is None: + if not os.path.exists(out_path): + os.makedirs(out_path) break - offset += len(data) - except Exception: # IOError? + else: + if not out_file: + # If out_path is a directory and we're expecting a file, bail out now. + if os.path.isdir(out_path): + break + out_file = open(out_path, 'wb') + out_file.write(data) + if len(data) < buffer_size: + break + offset += len(data) + except Exception: traceback.print_exc() raise errors.AnsibleError("failed to transfer file to %s" % out_path) + finally: + if out_file: + out_file.close() def close(self): if self.protocol and self.shell_id: diff --git a/lib/ansible/runner/shell_plugins/powershell.py b/lib/ansible/runner/shell_plugins/powershell.py index eead8d50b1..2fa6e8a08e 100644 --- a/lib/ansible/runner/shell_plugins/powershell.py +++ b/lib/ansible/runner/shell_plugins/powershell.py @@ -85,7 +85,21 @@ class ShellModule(object): def md5(self, path): path = _escape(path) - return _encode_script('''(Get-FileHash -Path "%s" -Algorithm MD5).Hash.ToLower();''' % path) + script = ''' + If (Test-Path -PathType Leaf "%(path)s") + { + (Get-FileHash -Path "%(path)s" -Algorithm MD5).Hash.ToLower(); + } + ElseIf (Test-Path -PathType Container "%(path)s") + { + Write-Host "3"; + } + Else + { + Write-Host "1"; + } + ''' % dict(path=path) + return _encode_script(script) def build_module_command(self, env_string, shebang, cmd, rm_tmp=None): cmd_parts = shlex.split(cmd, posix=False) diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py index c9d26e2564..e3ad20ad89 100644 --- a/lib/ansible/utils/__init__.py +++ b/lib/ansible/utils/__init__.py @@ -608,9 +608,9 @@ def md5s(data): return digest.hexdigest() def md5(filename): - ''' Return MD5 hex digest of local file, or None if file is not present. ''' + ''' Return MD5 hex digest of local file, None if file is not present or a directory. ''' - if not os.path.exists(filename): + if not os.path.exists(filename) or os.path.isdir(filename): return None digest = _md5() blocksize = 64 * 1024 diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 index 40bb5eba03..da2404d02f 100644 --- a/library/windows/slurp.ps1 +++ b/library/windows/slurp.ps1 @@ -22,26 +22,43 @@ $params = Parse-Args $args; $src = ''; If ($params.src.GetType) { - $src = $params.src; + $src = $params.src; } Else { - If ($params.path.GetType) - { - $src = $params.path; - } + If ($params.path.GetType) + { + $src = $params.path; + } } If (-not $src) { - + $result = New-Object psobject @{}; + Fail-Json $result "missing required argument: src"; } -$bytes = [System.IO.File]::ReadAllBytes($src); -$content = [System.Convert]::ToBase64String($bytes); +If (Test-Path $src) +{ + If ((Get-Item $src).Directory) # Only files have the .Directory attribute. + { + $bytes = [System.IO.File]::ReadAllBytes($src); + $content = [System.Convert]::ToBase64String($bytes); -$result = New-Object psobject @{ - changed = $false - encoding = "base64" -}; -Set-Attr $result "content" $content; -Exit-Json $result; + $result = New-Object psobject @{ + changed = $false + encoding = "base64" + }; + Set-Attr $result "content" $content; + Exit-Json $result; + } + Else + { + $result = New-Object psobject @{}; + Fail-Json $result ("is a directory: " + $src); + } +} +Else +{ + $result = New-Object psobject @{}; + Fail-Json $result ("file not found: " + $src); +} diff --git a/test/integration/roles/test_win_fetch/tasks/main.yml b/test/integration/roles/test_win_fetch/tasks/main.yml new file mode 100644 index 0000000000..b07b681bdd --- /dev/null +++ b/test/integration/roles/test_win_fetch/tasks/main.yml @@ -0,0 +1,168 @@ +# test code for the fetch module when using winrm connection +# (c) 2014, Chris Church + +# 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 . + +- name: clean out the test directory + local_action: file name={{ output_dir|mandatory }} state=absent + tags: me + +- name: create the test directory + local_action: file name={{ output_dir }} state=directory + tags: me + +- name: fetch a small file + fetch: src="C:/Windows/win.ini" dest={{ output_dir }} + register: fetch_small + +- name: check fetch small result + assert: + that: + - "fetch_small.changed" + +- name: check file created by fetch small + local_action: stat path={{ fetch_small.dest }} + register: fetch_small_stat + +- name: verify fetched small file exists locally + assert: + that: + - "fetch_small_stat.stat.exists" + - "fetch_small_stat.stat.isreg" + - "fetch_small_stat.stat.md5 == fetch_small.md5sum" + +- name: fetch the same small file + fetch: src="C:/Windows/win.ini" dest={{ output_dir }} + register: fetch_small_again + +- name: check fetch small result again + assert: + that: + - "not fetch_small_again.changed" + +- name: fetch a small file to flat namespace + fetch: src="C:/Windows/win.ini" dest="{{ output_dir }}/" flat=yes + register: fetch_flat + +- name: check fetch flat result + assert: + that: + - "fetch_flat.changed" + +- name: check file created by fetch flat + local_action: stat path="{{ output_dir }}/win.ini" + register: fetch_flat_stat + +- name: verify fetched file exists locally in output_dir + assert: + that: + - "fetch_flat_stat.stat.exists" + - "fetch_flat_stat.stat.isreg" + - "fetch_flat_stat.stat.md5 == fetch_flat.md5sum" + +- name: fetch a small file to flat directory (without trailing slash) + fetch: src="C:/Windows/win.ini" dest="{{ output_dir }}" flat=yes + register: fetch_flat_dir + ignore_errors: true + +- name: check fetch flat to directory result + assert: + that: + - "fetch_flat_dir|failed" + - "fetch_flat_dir.msg" + +- name: fetch a large binary file + fetch: src="C:/Windows/explorer.exe" dest={{ output_dir }} + register: fetch_large + +- name: check fetch large binary file result + assert: + that: + - "fetch_large.changed" + +- name: check file created by fetch large binary + local_action: stat path={{ fetch_large.dest }} + register: fetch_large_stat + +- name: verify fetched large file exists locally + assert: + that: + - "fetch_large_stat.stat.exists" + - "fetch_large_stat.stat.isreg" + - "fetch_large_stat.stat.md5 == fetch_large.md5sum" + +- name: fetch a large binary file again + fetch: src="C:/Windows/explorer.exe" dest={{ output_dir }} + register: fetch_large_again + +- name: check fetch large binary file result again + assert: + that: + - "not fetch_large_again.changed" + +- name: fetch a small file using backslashes in src path + fetch: src="C:\Windows\system.ini" dest={{ output_dir }} + register: fetch_small_bs + +- name: check fetch small result with backslashes + assert: + that: + - "fetch_small_bs.changed" + +- name: check file created by fetch small with backslashes + local_action: stat path={{ fetch_small_bs.dest }} + register: fetch_small_bs_stat + +- name: verify fetched small file with backslashes exists locally + assert: + that: + - "fetch_small_bs_stat.stat.exists" + - "fetch_small_bs_stat.stat.isreg" + - "fetch_small_bs_stat.stat.md5 == fetch_small_bs.md5sum" + +- name: attempt to fetch a non-existent file - do not fail on missing + fetch: src="C:/this_file_should_not_exist.txt" dest={{ output_dir }} + register: fetch_missing_nofail + +- name: check fetch missing no fail result + assert: + that: + - "not fetch_missing_nofail|failed" + - "fetch_missing_nofail.msg" + - "not fetch_missing_nofail|changed" + +- name: attempt to fetch a non-existent file - fail on missing + fetch: src="C:/this_file_should_not_exist.txt" dest={{ output_dir }} fail_on_missing=yes + register: fetch_missing + ignore_errors: true + +- name: check fetch missing with failure + assert: + that: + - "fetch_missing|failed" + - "fetch_missing.msg" + - "not fetch_missing|changed" + +- name: attempt to fetch a directory + fetch: src="C:\Windows" dest={{ output_dir }} + register: fetch_dir + ignore_errors: true + +- name: check fetch directory result + assert: + that: + - "fetch_dir|failed" + - "fetch_dir.msg" diff --git a/test/integration/roles/test_win_ping/tasks/main.yml b/test/integration/roles/test_win_ping/tasks/main.yml index 14c517cfa8..8bcbe910c4 100644 --- a/test/integration/roles/test_win_ping/tasks/main.yml +++ b/test/integration/roles/test_win_ping/tasks/main.yml @@ -1,4 +1,20 @@ ---- +# test code for the win_ping module +# (c) 2014, Chris Church + +# 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 . - name: test win_ping action: win_ping diff --git a/test/integration/roles/test_win_raw/tasks/main.yml b/test/integration/roles/test_win_raw/tasks/main.yml index a59ea3b624..aa15de9bc7 100644 --- a/test/integration/roles/test_win_raw/tasks/main.yml +++ b/test/integration/roles/test_win_raw/tasks/main.yml @@ -1,4 +1,20 @@ ---- +# test code for the raw module when using winrm connection +# (c) 2014, Chris Church + +# 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 . - name: run getmac raw: getmac diff --git a/test/integration/roles/test_win_script/files/test_script_with_errors.ps1 b/test/integration/roles/test_win_script/files/test_script_with_errors.ps1 index ad80f68f8b..2d60dc1f19 100644 --- a/test/integration/roles/test_win_script/files/test_script_with_errors.ps1 +++ b/test/integration/roles/test_win_script/files/test_script_with_errors.ps1 @@ -1,6 +1,4 @@ -# http://stackoverflow.com/questions/9948517/how-to-stop-a-powershell-script-on-the-first-error -#$ErrorActionPreference = "Stop"; -# http://stackoverflow.com/questions/15777492/why-are-my-powershell-exit-codes-always-0 +# Test script to make sure we handle non-zero exit codes. trap { diff --git a/test/integration/roles/test_win_script/tasks/main.yml b/test/integration/roles/test_win_script/tasks/main.yml index 99d94387f6..1edfd0b006 100644 --- a/test/integration/roles/test_win_script/tasks/main.yml +++ b/test/integration/roles/test_win_script/tasks/main.yml @@ -1,4 +1,20 @@ ---- +# test code for the script module when using winrm connection +# (c) 2014, Chris Church + +# 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 . - name: run simple test script script: test_script.ps1 diff --git a/test/integration/roles/test_win_slurp/tasks/main.yml b/test/integration/roles/test_win_slurp/tasks/main.yml new file mode 100644 index 0000000000..b72b74238b --- /dev/null +++ b/test/integration/roles/test_win_slurp/tasks/main.yml @@ -0,0 +1,77 @@ +# test code for the slurp module when using winrm connection +# (c) 2014, Chris Church + +# 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 . + +- name: test slurping an existing file + slurp: src="C:/Windows/win.ini" + register: slurp_existing + +- name: check slurp existing result + assert: + that: + - "slurp_existing.content" + - "slurp_existing.encoding == 'base64'" + - "not slurp_existing|changed" + - "not slurp_existing|failed" + +- name: test slurping a large binary file with path param and backslashes + slurp: path="C:\Windows\explorer.exe" + register: slurp_path_backslashes + +- name: check slurp result with path param and backslashes + assert: + that: + - "slurp_path_backslashes.content" + - "slurp_path_backslashes.encoding == 'base64'" + - "not slurp_path_backslashes|changed" + - "not slurp_path_backslashes|failed" + +- name: test slurping a non-existent file + slurp: src="C:/this_file_should_not_exist.txt" + register: slurp_missing + ignore_errors: true + +- name: check slurp missing result + assert: + that: + - "slurp_missing|failed" + - "slurp_missing.msg" + - "not slurp_missing|changed" + +- name: test slurping a directory + slurp: src="C:/Windows" + register: slurp_dir + ignore_errors: true + +- name: check slurp directory result + assert: + that: + - "slurp_dir|failed" + - "slurp_dir.msg" + - "not slurp_dir|changed" + +- name: test slurp with missing argument + action: slurp + register: slurp_no_args + ignore_errors: true + +- name: check slurp with missing argument result + assert: + that: + - "slurp_no_args|failed" + - "slurp_no_args.msg" + - "not slurp_no_args|changed" diff --git a/test/integration/test_winrm.yml b/test/integration/test_winrm.yml index 9ce95fc304..12c68e6dbc 100644 --- a/test/integration/test_winrm.yml +++ b/test/integration/test_winrm.yml @@ -6,3 +6,5 @@ - { role: test_win_raw, tags: test_win_raw } - { role: test_win_script, tags: test_win_script } - { role: test_win_ping, tags: test_win_ping } + - { role: test_win_slurp, tags: test_win_slurp } + - { role: test_win_fetch, tags: test_win_fetch } From 7631c005ca3121dedd82439bfc09ca919f2dfbcd Mon Sep 17 00:00:00 2001 From: Don Schenck Date: Thu, 19 Jun 2014 10:13:57 -0500 Subject: [PATCH 050/107] Added logging to UpgradeToPS3.ps1 UpgradeToPS3.ps1 failed when tested with Ansible. Added logging output to file C:\powershell\install.log. --- examples/scripts/UpgradeToPS3.ps1 | 2 +- library/windows/win_user.ps1 | 76 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 library/windows/win_user.ps1 diff --git a/examples/scripts/UpgradeToPS3.ps1 b/examples/scripts/UpgradeToPS3.ps1 index f6a303c951..83cc222b6e 100644 --- a/examples/scripts/UpgradeToPS3.ps1 +++ b/examples/scripts/UpgradeToPS3.ps1 @@ -68,4 +68,4 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -."$powershellpath\$filename" /quiet \ No newline at end of file +."$powershellpath\$filename" /quiet /log "C:\powershell\install.log" \ No newline at end of file diff --git a/library/windows/win_user.ps1 b/library/windows/win_user.ps1 new file mode 100644 index 0000000000..a89b9f218e --- /dev/null +++ b/library/windows/win_user.ps1 @@ -0,0 +1,76 @@ +#!powershell +# (c) 2014, Matt Martz , and others +# +# 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 . + +# WANT_JSON +# POWERSHELL_COMMON + +$params = Parse-Args $args; + +$result = New-Object psobject; +Set-Attr $result "changed" $false; + +If (-not $params.username.GetType) +{ + Fail-Json $result "missing required arguments: username" +} + +If (-not $params.password.GetType) +{ + Fail-Json $result "missing required arguments: password" +} + +$extra_args = "" +If ($params.extra_args.GetType) +{ + $extra_args = $params.extra_args; +} + +If ($params.creates.GetType -and $params.state.GetType -and $params.state -ne "absent") +{ + If (Does-User-Exist $params.username) + { + Exit-Json $result; + } +} + +$logfile = [IO.Path]::GetTempFileName(); +if ($params.state.GetType -and $params.state -eq "absent") +{ + NET USER $params.username $params.password /ADD +} + +Set-Attr $result "changed" $true; + +$logcontents = Get-Content $logfile; +Remove-Item $logfile; + +Set-Attr $result "log" $logcontents; + +Exit-Json $result; + +Function Does-User-Exist($username) +{ +$objComputer = [ADSI]("WinNT://$env:COMPUTERNAME,computer") + +$colUsers = ($objComputer.psbase.children | + Where-Object {$_.psBase.schemaClassName -eq "User"} | + Select-Object -expand Name) + +$blnFound = $colUsers -eq $username + +} \ No newline at end of file From 61c236be9e24d0046db901df764fac4517b014c4 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Wed, 18 Jun 2014 16:33:01 -0500 Subject: [PATCH 051/107] Fix for creating temp dir with older powershell versions. --- lib/ansible/runner/shell_plugins/powershell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/runner/shell_plugins/powershell.py b/lib/ansible/runner/shell_plugins/powershell.py index 2fa6e8a08e..5685960e8b 100644 --- a/lib/ansible/runner/shell_plugins/powershell.py +++ b/lib/ansible/runner/shell_plugins/powershell.py @@ -81,7 +81,7 @@ class ShellModule(object): def mkdtemp(self, basefile, system=False, mode=None): basefile = _escape(basefile) # FIXME: Support system temp path! - return _encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName;''' % basefile) + return _encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName | Write-Host -Separator '';''' % basefile) def md5(self, path): path = _escape(path) From 7309b2ad2a3ed06e7f6f600cb2c6d9001ffc65f3 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 16:37:20 -0500 Subject: [PATCH 052/107] Add explanation of windows upgrade details. --- examples/scripts/UpgradeToPS3.ps1 | 13 +++++++++++-- library/windows/win_get_url | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/scripts/UpgradeToPS3.ps1 b/examples/scripts/UpgradeToPS3.ps1 index 83cc222b6e..1c938d64f1 100644 --- a/examples/scripts/UpgradeToPS3.ps1 +++ b/examples/scripts/UpgradeToPS3.ps1 @@ -1,4 +1,13 @@ -# Upgrade to PowerShell 3.0 + +# Powershell script to upgrade a PowerShell 2.0 system to PowerShell 3.0 +# +# some Ansible modules that may use Powershell 3 features, so systems may need +# to be upgraded. This may be used by a sample playbook. Refer to the windows +# documentation on docs.ansible.com for details. +# +# - hosts: windows +# tasks: +# - script: upgrade_to_ps3.ps1 # Get version of OS @@ -68,4 +77,4 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -."$powershellpath\$filename" /quiet /log "C:\powershell\install.log" \ No newline at end of file +."$powershellpath\$filename" /quiet diff --git a/library/windows/win_get_url b/library/windows/win_get_url index 540b6b61b6..93f41acb7e 100644 --- a/library/windows/win_get_url +++ b/library/windows/win_get_url @@ -31,4 +31,4 @@ $ ansible -i hosts -c winrm -m win_get_url -a "url=http://www.example.com/earthr win_get_url: url: 'http://www.example.com/earthrise.jpg' dest: 'C:\Users\RandomUser\earthrise.jpg' -''' \ No newline at end of file +''' From 38338157b33111f06517b6216ca8c167a0d0276f Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 16:42:03 -0500 Subject: [PATCH 053/107] Add prep instructions. --- docsite/rst/intro_windows.rst | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 0319390c74..51e2d4db24 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -19,8 +19,8 @@ No additional software needs to be installed on the remote machines for Ansible .. _windows_installing: -Installing -`````````` +Installing on the Control Machine +`````````````````````````````````` On a Linux control machine:: @@ -55,6 +55,10 @@ communication channel that leverages Windows remoting:: ansible windows [-i inventory] -m ping --ask-vault-pass +If you haven't done anything to prep your systems yet, this won't work yet. This is covered in a later +section about how to enable powershell remoting - and if neccessary - how to upgrade powershell to +a version that is 3 or higher. + .. _windows_what_modules_are_available: What modules are available @@ -128,7 +132,6 @@ If your Windows firewall is enabled, you must also run the following command to # Windows 2008 / 2008R2 $ NetSH ADVFirewall Set AllProfiles Settings remotemanagement Enable -Best Practices By default, Powershell remoting enables an HTTP listener. The following commands enable an HTTPS listener, which secures communication between the Control Machine and windows. An SSL certificate for server authentication is required to create the HTTPS listener. The existence of an existing certificate in the computer account can be verified by using the MMC snap-in, as documented ' @@ -144,6 +147,23 @@ Alternatively, a self-signed SSL certificate can be generated in powershell usin $ Delete the http listener $ WinRM delete winrm/config/listener?Address=*+Transport=HTTP +Additionally, Powershell 3.0 or higher is needed for most modules. You can actually use a minimal +ansible example playbook to upgrade your windows systems from Powershell 2.0 to 3.0 in order to take +advantage of the *other* ansible modules. + +Looking at an ansible checkout, copy the examples/scripts/upgrade_ps2_to_3.ps1 script from the repo into +your local directory, and run a playbook that looks like the following:: + + - hosts: windows + gather_facts: no + tasks: + - script: upgrade_ps2_to_3.ps1 + +The hosts in the above group will then be running a new enough version of Powershell to be managed +by the full compliment of Ansible modules. + + + .. _windows_and_linux_control_machine: You Must Have a Linux Control Machine @@ -226,6 +246,8 @@ form of new modules, tweaks to existing modules, documentation, or something els How to write modules :doc:`playbooks` Learning ansible's configuration management language + `List of Windows Modules `_ + Windows specific module list, all implemented in powershell `Mailing List `_ Questions? Help? Ideas? Stop by the list on Google Groups `irc.freenode.net `_ From 128be9ea27277f40694847748ace56c0c01e6ae2 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 16:42:51 -0500 Subject: [PATCH 054/107] File rename. --- docsite/rst/intro_windows.rst | 4 ++-- examples/scripts/{UpgradeToPS3.ps1 => upgrade_to_ps3.ps1} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename examples/scripts/{UpgradeToPS3.ps1 => upgrade_to_ps3.ps1} (100%) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 51e2d4db24..26cdffb64b 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -151,13 +151,13 @@ Additionally, Powershell 3.0 or higher is needed for most modules. You can actu ansible example playbook to upgrade your windows systems from Powershell 2.0 to 3.0 in order to take advantage of the *other* ansible modules. -Looking at an ansible checkout, copy the examples/scripts/upgrade_ps2_to_3.ps1 script from the repo into +Looking at an ansible checkout, copy the examples/scripts/upgrade_to_ps3.ps1 script from the repo into your local directory, and run a playbook that looks like the following:: - hosts: windows gather_facts: no tasks: - - script: upgrade_ps2_to_3.ps1 + - script: upgrade_to_ps3.ps1 The hosts in the above group will then be running a new enough version of Powershell to be managed by the full compliment of Ansible modules. diff --git a/examples/scripts/UpgradeToPS3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 similarity index 100% rename from examples/scripts/UpgradeToPS3.ps1 rename to examples/scripts/upgrade_to_ps3.ps1 From 8eaa81d5a9fef50142c6da14e864be4d03013b53 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 16:53:32 -0500 Subject: [PATCH 055/107] Some docs restructuring to move the setup steps first. --- docsite/rst/intro_windows.rst | 106 +++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 26cdffb64b..940e964840 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -59,57 +59,12 @@ If you haven't done anything to prep your systems yet, this won't work yet. Thi section about how to enable powershell remoting - and if neccessary - how to upgrade powershell to a version that is 3 or higher. -.. _windows_what_modules_are_available: - -What modules are available -`````````````````````````` - -Most of the Ansible modules in core Ansible are written for a combination of Linux/Unix machines and arbitrary web services, though there are various -Windows modules as listed in the "windows" subcategory of the Ansible module index. - -Browse this index to see what is available. - -In many cases, it may not be neccessary to even write or use an Ansible module. - -In particular, the "win_script" module can be used to run arbitrary powershell scripts, allowing Windows administrators familiar with powershell a very native way to do things, as in the following playbook:: - - - hosts: windows - tasks: - - win_script: foo.ps1 --argument --other-argument - -.. _windows_developers_developers_developers: - -Developers: Supported modules and how it works -`````````````````````````````````````````````` - -Developing ansible modules are covered in a later section, with a focus on Linux/Unix. - -For Windows, ansible modules are implemented in Powershell. Skim the module development chapters before proceeding. - -Windows modules live in a "windows/" subfolder in the Ansible "library/" subtree. For example, if a module is named -"library/windows/win_ping", there will be embedded documentation in the "win_ping" file, and the actual powershell code will -live in a "win_ping.ps1" file. - -Modules (ps1 files) should start as follows:: - - #!powershell - # WANT_JSON - # POWERSHELL_COMMON - - # - # code goes here, reading in stdin as JSON and outputting JSON - -The above magic is neccessary to tell Ansible to mix in some common code and also know how to push modules out. - -Taking a look at the sources for win_ping and the equivalent will make it easier to understand how things work by following -the existing patterns. - -Additional modules may be submitted as pull requests to github. +You'll run this command again later though, to make sure everything is working. .. _windows_system_prep: -System Prep -``````````` +Windows System Prep +``````````````````` In order for Ansible to manage your windows machines, you will have to enable Powershell remoting first, which also enables WinRM. @@ -146,6 +101,19 @@ Alternatively, a self-signed SSL certificate can be generated in powershell usin $ winrm create winrm/config/Listener?Address=*+Transport=HTTPS  @{Hostname="host_name";CertificateThumbprint="certificate_thumbprint"} $ Delete the http listener $ WinRM delete winrm/config/listener?Address=*+Transport=HTTP + +It's time to verify things are working:: + + ansible windows [-i inventory] -m ping --ask-vault-pass + +However, if you are still running Powershell 2.0 on remote systems, it's time to use Ansible to upgrade powershell +before proceeding further, as some of the Ansible modules will require Powershell 3.0. Thankfully it's self +bootstrapping! + +.. _getting_to_powershell_three_or_higher: + +Getting to Powershell 3.0 or higher on Remote Systems +`````````````````````````````````````````````````````` Additionally, Powershell 3.0 or higher is needed for most modules. You can actually use a minimal ansible example playbook to upgrade your windows systems from Powershell 2.0 to 3.0 in order to take @@ -162,7 +130,49 @@ your local directory, and run a playbook that looks like the following:: The hosts in the above group will then be running a new enough version of Powershell to be managed by the full compliment of Ansible modules. +.. _what_windows_modules_are_available: +What modules are available +`````````````````````````` + +Most of the Ansible modules in core Ansible are written for a combination of Linux/Unix machines and arbitrary web services, though there are various +Windows modules as listed in the "windows" subcategory of the Ansible module index. + +Browse this index to see what is available. + +In many cases, it may not be neccessary to even write or use an Ansible module. + +In particular, the "win_script" module can be used to run arbitrary powershell scripts, allowing Windows administrators familiar with powershell a very native way to do things, as in the following playbook:: + + - hosts: windows + tasks: + - win_script: foo.ps1 --argument --other-argument + +.. _developers_developers_developers:: + +Developers: Supported modules and how it works +`````````````````````````````````````````````` + +Developing ansible modules are covered in a later section of the documentation, with a focus on Linux/Unix. +What if you want to write Windows modules for ansible though? + +For Windows, ansible modules are implemented in Powershell. Skim those Linux/Unix module development chapters before proceeding. + +Windows modules live in a "windows/" subfolder in the Ansible "library/" subtree. For example, if a module is named +"library/windows/win_ping", there will be embedded documentation in the "win_ping" file, and the actual powershell code will live in a "win_ping.ps1" file. Take a look at the sources and this will make more sense. + +Modules (ps1 files) should start as follows:: + + #!powershell + # WANT_JSON + # POWERSHELL_COMMON + + # + # code goes here, reading in stdin as JSON and outputting JSON + +The above magic is neccessary to tell Ansible to mix in some common code and also know how to push modules out. The common code contains some nice wrappers around working with hash data structures and emitting JSON results, and possibly a few mpmore useful things. Regular Ansible has this same concept for reusing Python code - this is just the windows equivalent. + +What modules you see in windows/ are just a start. Additional modules may be submitted as pull requests to github. .. _windows_and_linux_control_machine: From 118899c548f07253e1fd00f3a8e71c45b3759669 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 16:57:53 -0500 Subject: [PATCH 056/107] Fix hyperlink target. --- docsite/rst/intro_windows.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 940e964840..7670aeadf0 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -148,7 +148,7 @@ In particular, the "win_script" module can be used to run arbitrary powershell s tasks: - win_script: foo.ps1 --argument --other-argument -.. _developers_developers_developers:: +.. _developers_developers_developers: Developers: Supported modules and how it works `````````````````````````````````````````````` From e621fec7d331d14628f79281472771ea826b00aa Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Wed, 18 Jun 2014 16:59:10 -0500 Subject: [PATCH 057/107] Add notes on powershell script source. --- examples/scripts/upgrade_to_ps3.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index 1c938d64f1..a75b158a34 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -1,5 +1,6 @@ # Powershell script to upgrade a PowerShell 2.0 system to PowerShell 3.0 +# based on http://occasionalutility.blogspot.com/2013/11/everyday-powershell-part-7-powershell.html # # some Ansible modules that may use Powershell 3 features, so systems may need # to be upgraded. This may be used by a sample playbook. Refer to the windows From 25766a0d58481ee2ba840c6bd8cdd14468bd89f1 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Wed, 18 Jun 2014 17:06:49 -0500 Subject: [PATCH 058/107] Add prelim version of win_feature module (for installing packages, roles, features, etc) --- library/windows/win_feature.ps1 | 77 +++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 library/windows/win_feature.ps1 diff --git a/library/windows/win_feature.ps1 b/library/windows/win_feature.ps1 new file mode 100644 index 0000000000..d5d629a13d --- /dev/null +++ b/library/windows/win_feature.ps1 @@ -0,0 +1,77 @@ +#!powershell +# This file is part of Ansible. +# +# Copyright 2014, Paul Durivage +# +# 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 . + +# WANT_JSON +# POWERSHELL_COMMON + +Import-Module Servermanager; + +$params = Parse-Args $args; + +$result = New-Object psobject @{ + win_feature = New-Object psobject + changed = $false +} + +If ($params.name) { + $name = $params.name +} +Else { + Fail-Json $result "mising required argument: name" +} + +If ($params.state) { + $state = $params.state.ToString().ToLower() + If (!($state -eq 'present') -or ($state -eq 'absent')) { + Fail-Json $result "state must be 'present' or 'absent'" + } +} + +If ($state -eq "present") { + try { + $result = Add-WindowsFeature -Name $name + } + catch { + Fail-Json $_.Exception.Message + } +} +Elseif ($state -eq "absent") { + try { + $result = Remove-WindowsFeature -Name $name + } + catch { + Fail-Json $_.Exception.Message + } +} + +$feature_results = @() +ForEach ($item in $result.FeatureResult) { + $feature_results += New-Object psobject @{ + id = $item.id.ToString() + message = $item.Message.ToString() + restart_needed = $item.RestartNeeded.ToString() + skip_reason = $item.SkipReason.ToString() + success = $item.Success + } +} +Set-Attr $result.win_feature "feature_result" $feature_results +Set-Attr $result.win_feature "success" result.Success +Set-Attr $result.win_feature "exitcode" result.ExitCode.ToString() +Set-Attr $result.win_feature "restart_needed" result.RestartNeeded.ToString() + +Exit-Json $result; From 630c0cf57eb6aa68914102dab8916109440fab4b Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 19 Jun 2014 09:55:54 -0500 Subject: [PATCH 059/107] Fix up some formatting issues in intro_windows.rst --- docsite/rst/intro_windows.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 7670aeadf0..562ea82956 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -77,7 +77,7 @@ In the powershell session, run the following to enable PS Remoting and set the e $ Enable-PSRemoting -Force $ Set-ExecutionPolicy RemoteSigned -If your Windows firewall is enabled, you must also run the following command to allow firewall access to the public firewall profile:: +If your Windows firewall is enabled, you must also run the following command to allow firewall access to the public firewall profile: .. code-block:: bash @@ -97,9 +97,9 @@ Alternatively, a self-signed SSL certificate can be generated in powershell usin .. code-block:: bash - $ Create the https listener + # Create the https listener $ winrm create winrm/config/Listener?Address=*+Transport=HTTPS  @{Hostname="host_name";CertificateThumbprint="certificate_thumbprint"} - $ Delete the http listener + # Delete the http listener $ WinRM delete winrm/config/listener?Address=*+Transport=HTTP It's time to verify things are working:: From ba4b2d04d92d709a2fecc0c4195d2e6f9bd23e10 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 10:26:08 -0500 Subject: [PATCH 060/107] Fix logic to ensure state is either present or absent --- library/windows/win_feature.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/windows/win_feature.ps1 b/library/windows/win_feature.ps1 index d5d629a13d..cbb73789f4 100644 --- a/library/windows/win_feature.ps1 +++ b/library/windows/win_feature.ps1 @@ -37,8 +37,8 @@ Else { If ($params.state) { $state = $params.state.ToString().ToLower() - If (!($state -eq 'present') -or ($state -eq 'absent')) { - Fail-Json $result "state must be 'present' or 'absent'" + If (($state -ne 'present') -and ($state -ne 'absent')) { + Fail-Json $result "state is '$state'; must be 'present' or 'absent'" } } From af0bcb354d30d2816a2e5b1f3d3d7091259e9693 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 10:26:59 -0500 Subject: [PATCH 061/107] Ensure state is present if no value provided --- library/windows/win_feature.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/windows/win_feature.ps1 b/library/windows/win_feature.ps1 index cbb73789f4..4391de5eef 100644 --- a/library/windows/win_feature.ps1 +++ b/library/windows/win_feature.ps1 @@ -58,6 +58,9 @@ Elseif ($state -eq "absent") { Fail-Json $_.Exception.Message } } +Else { + $state = "present" +} $feature_results = @() ForEach ($item in $result.FeatureResult) { From 04d94ffb8f107e8df48ff6955f83c97257cf9dcb Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 10:28:01 -0500 Subject: [PATCH 062/107] Fix variable reference --- library/windows/win_feature.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/windows/win_feature.ps1 b/library/windows/win_feature.ps1 index 4391de5eef..6b9ecc445a 100644 --- a/library/windows/win_feature.ps1 +++ b/library/windows/win_feature.ps1 @@ -73,8 +73,8 @@ ForEach ($item in $result.FeatureResult) { } } Set-Attr $result.win_feature "feature_result" $feature_results -Set-Attr $result.win_feature "success" result.Success -Set-Attr $result.win_feature "exitcode" result.ExitCode.ToString() -Set-Attr $result.win_feature "restart_needed" result.RestartNeeded.ToString() +Set-Attr $result.win_feature "success" $result.Success +Set-Attr $result.win_feature "exitcode" $result.ExitCode.ToString() +Set-Attr $result.win_feature "restart_needed" $result.RestartNeeded.ToString() Exit-Json $result; From 90c98ada7cccc3f8fddfeb83cfc97409b392184d Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 19 Jun 2014 10:36:53 -0500 Subject: [PATCH 063/107] Add ConvertTo-Bool filter function in powershell common code --- lib/ansible/module_utils/powershell.ps1 | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index a911ea95dd..86c3c82e5b 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -64,3 +64,26 @@ Function Fail-Json($obj, $message) echo $obj | ConvertTo-Json Exit 1 } + +# Helper filter/pipeline function to convert a value to boolean following current +# Ansible practices +Function ConvertTo-Bool +{ + param( + [parameter(valuefrompipeline=$true)] + $obj + ) + + $boolean_strings = "yes", "on", "1", "true", 1 + $obj_string = [string]$obj + + if (($obj.GetType().Name -eq "Boolean" -and $obj) -or $boolean_strings -contains $obj_string.ToLower()) + { + $true + } + Else + { + $false + } + return +} From edbe7a4514500c75611834f7823b921968a62555 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 11:20:11 -0500 Subject: [PATCH 064/107] Add tests for win_stat module. --- library/windows/win_stat | 3 +- library/windows/win_stat.ps1 | 8 +- .../roles/test_win_stat/tasks/main.yml | 80 +++++++++++++++++++ test/integration/test_winrm.yml | 1 + 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 test/integration/roles/test_win_stat/tasks/main.yml diff --git a/library/windows/win_stat b/library/windows/win_stat index 5aba801a67..c98cd55f59 100644 --- a/library/windows/win_stat +++ b/library/windows/win_stat @@ -27,7 +27,8 @@ description: options: path: description: - - The full path of the file/object to get the facts of + - The full path of the file/object to get the facts of; both forward and + back slashes are accepted. required: true default: null aliases: [] diff --git a/library/windows/win_stat.ps1 b/library/windows/win_stat.ps1 index fa5596ec1b..a5748a8bda 100644 --- a/library/windows/win_stat.ps1 +++ b/library/windows/win_stat.ps1 @@ -19,16 +19,20 @@ $params = Parse-Args $args; -$path = ''; +$path = $FALSE; If ($params.path.GetType) { $path = $params.path; } +If ($path -eq $FALSE) +{ + Fail-Json (New-Object psobject) "missing required argument: path"; +} $get_md5 = $TRUE; If ($params.get_md5.GetType) { - $get_md5 = $params.get_md5; + $get_md5 = $params.get_md5 | ConvertTo-Bool; } $result = New-Object psobject @{ diff --git a/test/integration/roles/test_win_stat/tasks/main.yml b/test/integration/roles/test_win_stat/tasks/main.yml new file mode 100644 index 0000000000..a526976ec9 --- /dev/null +++ b/test/integration/roles/test_win_stat/tasks/main.yml @@ -0,0 +1,80 @@ +# test code for the win_stat module +# (c) 2014, Chris Church + +# 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 . + +- name: test win_stat module on file + win_stat: path="C:/Windows/win.ini" + register: win_stat_file + +- name: check win_stat file result + assert: + that: + - "win_stat_file.stat.exists" + - "not win_stat_file.stat.isdir" + - "win_stat_file.stat.size > 0" + - "win_stat_file.stat.md5" + - "not win_stat_file|failed" + - "not win_stat_file|changed" + +- name: test win_stat module on file without md5 and backslashes + win_stat: path="C:\Windows\win.ini" get_md5=no + register: win_stat_file_no_md5 + +- name: check win_stat file result without md + assert: + that: + - "win_stat_file_no_md5.stat.exists" + - "not win_stat_file_no_md5.stat.isdir" + - "win_stat_file_no_md5.stat.size > 0" + - "not win_stat_file_no_md5.stat.md5|default('')" + - "not win_stat_file_no_md5|failed" + - "not win_stat_file_no_md5|changed" + +- name: test win_stat module on directory + win_stat: path="C:\\Windows" + register: win_stat_dir + +- name: check win_stat dir result + assert: + that: + - "win_stat_dir.stat.exists" + - "win_stat_dir.stat.isdir" + - "not win_stat_dir|failed" + - "not win_stat_dir|changed" + +- name: test win_stat module non-existent path + win_stat: path="C:/this_file_should_not_exist.txt" + register: win_stat_missing + +- name: check win_stat missing result + assert: + that: + - "not win_stat_missing.stat.exists" + - "not win_stat_missing|failed" + - "not win_stat_missing|changed" + +- name: test win_stat module without path argument + action: win_stat + register: win_stat_no_args + ignore_errors: true + +- name: check win_stat result witn no path argument + assert: + that: + - "win_stat_no_args|failed" + - "win_stat_no_args.msg" + - "not win_stat_no_args|changed" diff --git a/test/integration/test_winrm.yml b/test/integration/test_winrm.yml index 12c68e6dbc..f8bde65912 100644 --- a/test/integration/test_winrm.yml +++ b/test/integration/test_winrm.yml @@ -8,3 +8,4 @@ - { role: test_win_ping, tags: test_win_ping } - { role: test_win_slurp, tags: test_win_slurp } - { role: test_win_fetch, tags: test_win_fetch } + - { role: test_win_stat, tags: test_win_stat } From 5c6136957496fe1da79e9a28b5c25d0db9d9c82a Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 11:20:36 -0500 Subject: [PATCH 065/107] Add data option to docs for win_ping. --- library/windows/win_ping | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/library/windows/win_ping b/library/windows/win_ping index 4e7e32c45a..de32877d61 100644 --- a/library/windows/win_ping +++ b/library/windows/win_ping @@ -27,12 +27,21 @@ 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 -options: {} + - Checks management connectivity of a windows host +options: + data: + description: + - Alternate data to return instead of 'pong' + required: false + default: 'pong' + aliases: [] author: Chris Church ''' EXAMPLES = ''' +# Test connectivity to a windows host +ansible winserver -m win_ping + # Example from an Ansible Playbook - action: win_ping ''' From e4e610565c4e6e5121e173ca68e96d705323a223 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 19 Jun 2014 11:32:32 -0500 Subject: [PATCH 066/107] Add convenience function (Get-Attr) for getting an attribute/member from a powershell psobject --- lib/ansible/module_utils/powershell.ps1 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index 86c3c82e5b..37366f0d61 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -47,6 +47,23 @@ Function Set-Attr($obj, $name, $value) $obj | Add-Member -Force -MemberType NoteProperty -Name $name -Value $value } +# Helper function to get an "attribute" from a psobject instance in powershell. +# This is a convenience to make getting Members from an object easier and +# slightly more pythonic +# Example: $attr = Get-Attr $response "code" -default "1" +Function Get-Attr($obj, $name, $default = $null) +{ + If ($obj.$name.GetType) + { + $obj.$name + } + Else + { + $default + } + return +} + # Helper function to convert a powershell object to JSON to echo it, exiting # the script Function Exit-Json($obj) From df8866b8bd1b9672db236a1da50dee9fd3251366 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 19 Jun 2014 11:32:48 -0500 Subject: [PATCH 067/107] Add examples for how to use powershell common functions --- lib/ansible/module_utils/powershell.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index 37366f0d61..919bf19532 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -29,6 +29,7 @@ # Helper function to parse Ansible JSON arguments from a file passed as # the single argument to the module +# Example: $params = Parse-Args $args Function Parse-Args($arguments) { $parameters = New-Object psobject; @@ -42,6 +43,7 @@ Function Parse-Args($arguments) # Helper function to set an "attribute" on a psobject instance in powershell. # This is a convenience to make adding Members to the object easier and # slightly more pythonic +# Example: Set-Attr $result "changed" $true Function Set-Attr($obj, $name, $value) { $obj | Add-Member -Force -MemberType NoteProperty -Name $name -Value $value @@ -66,6 +68,7 @@ Function Get-Attr($obj, $name, $default = $null) # Helper function to convert a powershell object to JSON to echo it, exiting # the script +# Example: Exit-Json $result Function Exit-Json($obj) { echo $obj | ConvertTo-Json @@ -74,6 +77,7 @@ Function Exit-Json($obj) # Helper function to add the "msg" property and "failed" property, convert the # powershell object to JSON and echo it, exiting the script +# Example: Fail-Json $result "This is the failure message" Function Fail-Json($obj, $message) { Set-Attr $obj "msg" $message @@ -84,6 +88,7 @@ Function Fail-Json($obj, $message) # Helper filter/pipeline function to convert a value to boolean following current # Ansible practices +# Example: $is_true = "true" | ConvertTo-Bool Function ConvertTo-Bool { param( From 43a7a5a990febdc11381376f10e5a22eb271b3d4 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 11:33:33 -0500 Subject: [PATCH 068/107] Collapse extra whitespace in encoded powershell scripts. --- lib/ansible/runner/shell_plugins/powershell.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ansible/runner/shell_plugins/powershell.py b/lib/ansible/runner/shell_plugins/powershell.py index 5685960e8b..2047913ad7 100644 --- a/lib/ansible/runner/shell_plugins/powershell.py +++ b/lib/ansible/runner/shell_plugins/powershell.py @@ -46,6 +46,7 @@ def _escape(value, include_vars=False): def _encode_script(script, as_list=False): '''Convert a PowerShell script to a single base64-encoded command.''' + script = '\n'.join([x.strip() for x in script.splitlines() if x.strip()]) encoded_script = base64.b64encode(script.encode('utf-16-le')) cmd_parts = _common_args + ['-EncodedCommand', encoded_script] if as_list: From 7e055ec6cc2539bcb55f7cfe414d6347b94cca4b Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 11:40:17 -0500 Subject: [PATCH 069/107] Remove unnecessary obj --- examples/scripts/upgrade_to_ps3.ps1 | 5 +++-- library/windows/win_feature.ps1 | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index a75b158a34..794da55bd5 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -17,6 +17,7 @@ # 6.2 is 2012 # 6.3 is 2012 R2 +Start-Transcript "C:\powershell\scriptlog.txt" if ($PSVersionTable.psversion.Major -ge 3) { @@ -77,5 +78,5 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" - -."$powershellpath\$filename" /quiet +Stop-Transcript +."$powershellpath\$filename" /quiet /log "C:\powershell\install.log" diff --git a/library/windows/win_feature.ps1 b/library/windows/win_feature.ps1 index 6b9ecc445a..bd2b287419 100644 --- a/library/windows/win_feature.ps1 +++ b/library/windows/win_feature.ps1 @@ -24,7 +24,6 @@ Import-Module Servermanager; $params = Parse-Args $args; $result = New-Object psobject @{ - win_feature = New-Object psobject changed = $false } From f42905a9ccbf62bd3d0e2b4ffc760a90d5bd37b0 Mon Sep 17 00:00:00 2001 From: Craig Ackerman Date: Thu, 19 Jun 2014 16:59:57 +0000 Subject: [PATCH 070/107] Added commands to allow Windows firewall access to WinRM HTTPS listener --- docsite/rst/intro_windows.rst | 7 +++++++ examples/scripts/upgrade_to_ps3.ps1 | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 562ea82956..645ea98c75 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -101,6 +101,13 @@ Alternatively, a self-signed SSL certificate can be generated in powershell usin $ winrm create winrm/config/Listener?Address=*+Transport=HTTPS  @{Hostname="host_name";CertificateThumbprint="certificate_thumbprint"} # Delete the http listener $ WinRM delete winrm/config/listener?Address=*+Transport=HTTP + +Again, if your Windows firewall is enabled, you must also run the following command to allow firewall access to the public firewall profile: + + .. code-block:: bash + + # Windows 2008 / 2008R2 / 2012 / 2012R2 + $ netsh advfirewall firewall add rule name="Allow WinRM HTTPS" dir=in localport=5986 protocol=TCP action=allow It's time to verify things are working:: diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index 794da55bd5..f9cbb2de48 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -17,7 +17,6 @@ # 6.2 is 2012 # 6.3 is 2012 R2 -Start-Transcript "C:\powershell\scriptlog.txt" if ($PSVersionTable.psversion.Major -ge 3) { @@ -33,6 +32,7 @@ function download-file $client = new-object system.net.WebClient $client.Headers.Add("user-agent", "PowerShell") $client.downloadfile($path, $local) + write-host "file downloaded successfully" } if (!(test-path $powershellpath)) @@ -78,5 +78,5 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -Stop-Transcript + ."$powershellpath\$filename" /quiet /log "C:\powershell\install.log" From cd3edf1ebabe0bf841f7d7192e2707e73c91fb68 Mon Sep 17 00:00:00 2001 From: Craig Ackerman Date: Thu, 19 Jun 2014 17:08:17 +0000 Subject: [PATCH 071/107] Added FW commands to allow access to HTTPS listener --- docsite/rst/intro_windows.rst | 2 +- examples/scripts/upgrade_to_ps3.ps1 | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 645ea98c75..9032a9df38 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -102,7 +102,7 @@ Alternatively, a self-signed SSL certificate can be generated in powershell usin # Delete the http listener $ WinRM delete winrm/config/listener?Address=*+Transport=HTTP -Again, if your Windows firewall is enabled, you must also run the following command to allow firewall access to the public firewall profile: +Again, if your Windows firewall is enabled, the following command to allow firewall access to the HTTPS listener: .. code-block:: bash diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index f9cbb2de48..1e36607f81 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -32,7 +32,6 @@ function download-file $client = new-object system.net.WebClient $client.Headers.Add("user-agent", "PowerShell") $client.downloadfile($path, $local) - write-host "file downloaded successfully" } if (!(test-path $powershellpath)) @@ -56,6 +55,7 @@ if (!(test-path $powershellpath)) # If the Operating System is above 6.2, then you already have PowerShell Version > 3 if ([Environment]::OSVersion.Version.Major -gt 6) { + write-host "OS is new; upgrade not needed." Exit } @@ -78,5 +78,6 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" - +write-host "Download successful" +exit ."$powershellpath\$filename" /quiet /log "C:\powershell\install.log" From e5399b12b9b83063159acff728bea970b609e48c Mon Sep 17 00:00:00 2001 From: Don Schenck Date: Thu, 19 Jun 2014 12:07:20 -0500 Subject: [PATCH 072/107] Pipe Get-Process to see what's running --- examples/scripts/upgrade_to_ps3.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index 1e36607f81..e83d7828a9 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -28,6 +28,7 @@ $powershellpath = "C:\powershell" function download-file { + Get-Process | Out-File c:\powershell\whatisrunningrightnow.txt param ([string]$path, [string]$local) $client = new-object system.net.WebClient $client.Headers.Add("user-agent", "PowerShell") @@ -78,6 +79,5 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -write-host "Download successful" -exit + ."$powershellpath\$filename" /quiet /log "C:\powershell\install.log" From f86db3a4dc852c7d49ed02e27d765bd9461ac8d5 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 12:14:57 -0500 Subject: [PATCH 073/107] Update slurp/win_ping/win_stat to use Get-Attr. --- library/windows/slurp.ps1 | 50 ++++++++++++------------------------ library/windows/win_ping.ps1 | 7 ++--- library/windows/win_stat.ps1 | 12 ++------- 3 files changed, 20 insertions(+), 49 deletions(-) diff --git a/library/windows/slurp.ps1 b/library/windows/slurp.ps1 index da2404d02f..edf1da7635 100644 --- a/library/windows/slurp.ps1 +++ b/library/windows/slurp.ps1 @@ -19,46 +19,28 @@ $params = Parse-Args $args; -$src = ''; -If ($params.src.GetType) -{ - $src = $params.src; -} -Else -{ - If ($params.path.GetType) - { - $src = $params.path; - } -} +$src = Get-Attr $params "src" (Get-Attr $params "path" $FALSE); If (-not $src) { - $result = New-Object psobject @{}; - Fail-Json $result "missing required argument: src"; + Fail-Json (New-Object psobject) "missing required argument: src"; } -If (Test-Path $src) +If (Test-Path -PathType Leaf $src) { - If ((Get-Item $src).Directory) # Only files have the .Directory attribute. - { - $bytes = [System.IO.File]::ReadAllBytes($src); - $content = [System.Convert]::ToBase64String($bytes); - - $result = New-Object psobject @{ - changed = $false - encoding = "base64" - }; - Set-Attr $result "content" $content; - Exit-Json $result; - } - Else - { - $result = New-Object psobject @{}; - Fail-Json $result ("is a directory: " + $src); - } + $bytes = [System.IO.File]::ReadAllBytes($src); + $content = [System.Convert]::ToBase64String($bytes); + $result = New-Object psobject @{ + changed = $false + encoding = "base64" + content = $content + }; + Exit-Json $result; +} +ElseIf (Test-Path -PathType Container $src) +{ + Fail-Json (New-Object psobject) ("is a directory: " + $src); } Else { - $result = New-Object psobject @{}; - Fail-Json $result ("file not found: " + $src); + Fail-Json (New-Object psobject) ("file not found: " + $src); } diff --git a/library/windows/win_ping.ps1 b/library/windows/win_ping.ps1 index ec6d530025..98f1415e29 100644 --- a/library/windows/win_ping.ps1 +++ b/library/windows/win_ping.ps1 @@ -19,14 +19,11 @@ $params = Parse-Args $args; -$data = 'pong'; -If ($params.data.GetType) -{ - $data = $params.data; -} +$data = Get-Attr $params "data" "pong"; $result = New-Object psobject @{ changed = $false ping = $data }; + Exit-Json $result; diff --git a/library/windows/win_stat.ps1 b/library/windows/win_stat.ps1 index a5748a8bda..f60bc577ec 100644 --- a/library/windows/win_stat.ps1 +++ b/library/windows/win_stat.ps1 @@ -19,21 +19,13 @@ $params = Parse-Args $args; -$path = $FALSE; -If ($params.path.GetType) -{ - $path = $params.path; -} +$path = Get-Attr $params "path" $FALSE; If ($path -eq $FALSE) { Fail-Json (New-Object psobject) "missing required argument: path"; } -$get_md5 = $TRUE; -If ($params.get_md5.GetType) -{ - $get_md5 = $params.get_md5 | ConvertTo-Bool; -} +$get_md5 = Get-Attr $params "get_md5" $TRUE | ConvertTo-Bool; $result = New-Object psobject @{ stat = New-Object psobject From 09dd5352962e440cf3255c5149e99e5cfa2988c0 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 19 Jun 2014 12:16:43 -0500 Subject: [PATCH 074/107] Make helper functions more robust against undefined powershell objects --- lib/ansible/module_utils/powershell.ps1 | 29 ++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index 919bf19532..5cae79df6b 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -46,6 +46,12 @@ Function Parse-Args($arguments) # Example: Set-Attr $result "changed" $true Function Set-Attr($obj, $name, $value) { + # If the provided $obj is undefined, define one to be nice + If (-not $obj.GetType) + { + $obj = New-Object psobject + } + $obj | Add-Member -Force -MemberType NoteProperty -Name $name -Value $value } @@ -55,6 +61,8 @@ Function Set-Attr($obj, $name, $value) # Example: $attr = Get-Attr $response "code" -default "1" Function Get-Attr($obj, $name, $default = $null) { + # Check if the provided Member $name exists in $obj and return it or the + # default If ($obj.$name.GetType) { $obj.$name @@ -71,6 +79,12 @@ Function Get-Attr($obj, $name, $default = $null) # Example: Exit-Json $result Function Exit-Json($obj) { + # If the provided $obj is undefined, define one to be nice + If (-not $obj.GetType) + { + $obj = New-Object psobject + } + echo $obj | ConvertTo-Json Exit } @@ -78,8 +92,21 @@ Function Exit-Json($obj) # Helper function to add the "msg" property and "failed" property, convert the # powershell object to JSON and echo it, exiting the script # Example: Fail-Json $result "This is the failure message" -Function Fail-Json($obj, $message) +Function Fail-Json($obj, $message = $null) { + # If we weren't given 2 args, and the only arg was a string, create a new + # psobject and use the arg as the failure message + If ($message -eq $null -and $obj.GetType().Name -eq "String") + { + $message = $obj + $obj = New-Object psobject + } + # If the first args is undefined or not an object, make it an object + ElseIf (-not $obj.GetType -or $obj.GetType().Name -ne "PSCustomObject") + { + $obj = New-Object psobject + } + Set-Attr $obj "msg" $message Set-Attr $obj "failed" $true echo $obj | ConvertTo-Json From ab8f7289b05b7cdc384bac81f5e8a56db11ac233 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 12:17:10 -0500 Subject: [PATCH 075/107] Renaming and refactoring of names, variables --- library/windows/win_feature.ps1 | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/library/windows/win_feature.ps1 b/library/windows/win_feature.ps1 index bd2b287419..cc626668f0 100644 --- a/library/windows/win_feature.ps1 +++ b/library/windows/win_feature.ps1 @@ -40,13 +40,20 @@ If ($params.state) { Fail-Json $result "state is '$state'; must be 'present' or 'absent'" } } +Elseif (!$params.state) { + $state = "present" +} + +If ($params.restart) { + $restart = $params.restart | ConvertTo-Bool +} If ($state -eq "present") { try { $result = Add-WindowsFeature -Name $name } catch { - Fail-Json $_.Exception.Message + Fail-Json $result $_.Exception.Message } } Elseif ($state -eq "absent") { @@ -54,26 +61,28 @@ Elseif ($state -eq "absent") { $result = Remove-WindowsFeature -Name $name } catch { - Fail-Json $_.Exception.Message + Fail-Json $result $_.Exception.Message } } -Else { - $state = "present" -} $feature_results = @() ForEach ($item in $result.FeatureResult) { $feature_results += New-Object psobject @{ id = $item.id.ToString() + display_name = $item.DisplayName message = $item.Message.ToString() restart_needed = $item.RestartNeeded.ToString() skip_reason = $item.SkipReason.ToString() - success = $item.Success + success = $item.Success.ToString() } } -Set-Attr $result.win_feature "feature_result" $feature_results -Set-Attr $result.win_feature "success" $result.Success -Set-Attr $result.win_feature "exitcode" $result.ExitCode.ToString() -Set-Attr $result.win_feature "restart_needed" $result.RestartNeeded.ToString() +Set-Attr $result "feature_result" $installed_features +Set-Attr $result "feature_success" $featureresult.Success.ToString() +Set-Attr $result "feature_exitcode" $featureresult.ExitCode.ToString() +Set-Attr $result "feature_restart_needed" $featureresult.RestartNeeded.ToString() + +If ($result.feature_result.Length -gt 0) { + $result.changed = $true +} Exit-Json $result; From 10a526e020f09234d65c03a5289548f049b6f357 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 12:17:38 -0500 Subject: [PATCH 076/107] Add restart functionality --- library/windows/win_feature.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/library/windows/win_feature.ps1 b/library/windows/win_feature.ps1 index cc626668f0..0808aafe24 100644 --- a/library/windows/win_feature.ps1 +++ b/library/windows/win_feature.ps1 @@ -50,7 +50,12 @@ If ($params.restart) { If ($state -eq "present") { try { - $result = Add-WindowsFeature -Name $name + if ($restart) { + $featureresult = Add-WindowsFeature -Name $name -Restart + } + else { + $featureresult = Add-WindowsFeature -Name $name + } } catch { Fail-Json $result $_.Exception.Message From bceaf96fd610fb6f9755cbd46e6de509ca6552e6 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 12:17:48 -0500 Subject: [PATCH 077/107] Add comments --- examples/scripts/upgrade_to_ps3.ps1 | 2 +- library/windows/win_feature.ps1 | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index e83d7828a9..c9096b22f4 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -78,6 +78,6 @@ else } $FileName = $DownLoadUrl.Split('/')[-1] -download-file $downloadurl "$powershellpath\$filename" +# download-file $downloadurl "$powershellpath\$filename" ."$powershellpath\$filename" /quiet /log "C:\powershell\install.log" diff --git a/library/windows/win_feature.ps1 b/library/windows/win_feature.ps1 index 0808aafe24..698d78dfca 100644 --- a/library/windows/win_feature.ps1 +++ b/library/windows/win_feature.ps1 @@ -63,16 +63,23 @@ If ($state -eq "present") { } Elseif ($state -eq "absent") { try { - $result = Remove-WindowsFeature -Name $name + if ($restart) { + $featureresult = Remove-WindowsFeature -Name $name -Restart + } + else { + $featureresult = Remove-WindowsFeature -Name $name + } } catch { Fail-Json $result $_.Exception.Message } } -$feature_results = @() -ForEach ($item in $result.FeatureResult) { - $feature_results += New-Object psobject @{ +# Loop through results and create a hash containing details about +# each role/feature that is installed/removed +$installed_features = @() +ForEach ($item in $featureresult.FeatureResult) { + $installed_features += New-Object psobject @{ id = $item.id.ToString() display_name = $item.DisplayName message = $item.Message.ToString() From 5b85252043ec16271989dad90a54861360ed3fd4 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 12:25:44 -0500 Subject: [PATCH 078/107] Add tests for windows setup module. --- examples/scripts/upgrade_to_ps3.ps1 | 5 ++- .../roles/test_win_setup/tasks/main.yml | 35 +++++++++++++++++++ test/integration/test_winrm.yml | 1 + 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 test/integration/roles/test_win_setup/tasks/main.yml diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index c9096b22f4..567dc7ba87 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -28,7 +28,6 @@ $powershellpath = "C:\powershell" function download-file { - Get-Process | Out-File c:\powershell\whatisrunningrightnow.txt param ([string]$path, [string]$local) $client = new-object system.net.WebClient $client.Headers.Add("user-agent", "PowerShell") @@ -78,6 +77,6 @@ else } $FileName = $DownLoadUrl.Split('/')[-1] -# download-file $downloadurl "$powershellpath\$filename" - +download-file $downloadurl "$powershellpath\$filename" +write-host "$powershellpath\$filename" ."$powershellpath\$filename" /quiet /log "C:\powershell\install.log" diff --git a/test/integration/roles/test_win_setup/tasks/main.yml b/test/integration/roles/test_win_setup/tasks/main.yml new file mode 100644 index 0000000000..a62e49936f --- /dev/null +++ b/test/integration/roles/test_win_setup/tasks/main.yml @@ -0,0 +1,35 @@ +# test code for the setup module when using winrm connection +# (c) 2014, Chris Church + +# 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 . + +- name: test setup module + action: setup + register: setup_result + +- name: check setup result + assert: + that: + - "not setup_result|failed" + - "not setup_result|changed" + - "setup_result.ansible_facts" + - "setup_result.ansible_facts.ansible_os_family == 'Windows'" + - "setup_result.ansible_facts.ansible_distribution" + - "setup_result.ansible_facts.ansible_distribution_version" + - "setup_result.ansible_facts.ansible_fqdn" + - "setup_result.ansible_facts.ansible_hostname" + - "setup_result.ansible_facts.ansible_ip_addresses" + - "setup_result.ansible_facts.ansible_system" diff --git a/test/integration/test_winrm.yml b/test/integration/test_winrm.yml index f8bde65912..81443d4dbf 100644 --- a/test/integration/test_winrm.yml +++ b/test/integration/test_winrm.yml @@ -6,6 +6,7 @@ - { role: test_win_raw, tags: test_win_raw } - { role: test_win_script, tags: test_win_script } - { role: test_win_ping, tags: test_win_ping } + - { role: test_win_setup, tags: test_win_setup } - { role: test_win_slurp, tags: test_win_slurp } - { role: test_win_fetch, tags: test_win_fetch } - { role: test_win_stat, tags: test_win_stat } From e2f5d40a6b12a0c1e865cf4bffcdb8a44f396cf0 Mon Sep 17 00:00:00 2001 From: Don Schenck Date: Thu, 19 Jun 2014 13:15:01 -0500 Subject: [PATCH 079/107] Changed launch Using Start-Process --- examples/scripts/upgrade_to_ps3.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index 567dc7ba87..6604cd30b3 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -78,5 +78,5 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -write-host "$powershellpath\$filename" -."$powershellpath\$filename" /quiet /log "C:\powershell\install.log" + +Start-Process -FilePath "$powershellpath\$filename" -ArgumentList /quiet, /log "C:\powershell\install.log" From 8012fdc4481f816682504ba7669fe97e6cc7e9f1 Mon Sep 17 00:00:00 2001 From: Don Schenck Date: Thu, 19 Jun 2014 13:16:08 -0500 Subject: [PATCH 080/107] Start-Process line was wrong Fixed --- examples/scripts/upgrade_to_ps3.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index 6604cd30b3..693088b75f 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -79,4 +79,4 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -Start-Process -FilePath "$powershellpath\$filename" -ArgumentList /quiet, /log "C:\powershell\install.log" +Start-Process -FilePath "$powershellpath\$filename" -ArgumentList /quiet From 61a06473ec93a097d47099ef4312ed3be1b1d9d9 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 13:16:55 -0500 Subject: [PATCH 081/107] Add documentation for win_feature module --- library/windows/win_feature | 54 +++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 library/windows/win_feature diff --git a/library/windows/win_feature b/library/windows/win_feature new file mode 100644 index 0000000000..6c64c06848 --- /dev/null +++ b/library/windows/win_feature @@ -0,0 +1,54 @@ +DOCUMENTATION = ''' +--- +module: win_get_url +version_added: "1.7" +short_description: Fetches a file from a given URL +description: + - Fetches a file from a URL and saves to locally +options: + name: + description: + - Names of roles or features to install as a single feature or a comma-separated list of features + required: true + default: null + aliases: [] + state: + description: + - State of the features or roles on the system + required: false + choices: + - present + - absent + default: present + aliases: [] + restart: + description: + - Restarts the computer automatically when installation is complete, if restarting is required by the roles or features installed. + choices: + - yes + - no + default: null + aliases: [] +author: Paul Durivage +''' + +EXAMPLES = ''' +# This installs IIS. +# The names of features available for install can be run by running the following Powershell Command: +# PS C:\Users\Administrator> Import-Module ServerManager; Get-WindowsFeature +$ ansible -i hosts -m win_feature -a "name=Web-Server" all +$ ansible -i hosts -m win_feature -a "name=Web-Server,Web-Common-Http" all + + +# Playbook example +--- +- name: Install IIS + hosts: all + gather_facts: false + tasks: + - name: Install IIS + win_feature: + name: "Web-Server" + state: absent + restart: yes +''' From 9c4220832a7f374e106c0b4304c0b662cda5a9c9 Mon Sep 17 00:00:00 2001 From: Don Schenck Date: Thu, 19 Jun 2014 13:21:02 -0500 Subject: [PATCH 082/107] Start-Process Debugging --- examples/scripts/upgrade_to_ps3.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index 693088b75f..af341d8eef 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -79,4 +79,4 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -Start-Process -FilePath "$powershellpath\$filename" -ArgumentList /quiet +Start-Process -FilePath "$powershellpath\$filename /quiet" -WindowStyle Hidden From 618b47cd77190656dc822a1c4b50b1414455ee43 Mon Sep 17 00:00:00 2001 From: Don Schenck Date: Thu, 19 Jun 2014 13:22:52 -0500 Subject: [PATCH 083/107] Added -Wait flag to Start-Process Must wait in order for script to be available --- examples/scripts/upgrade_to_ps3.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index af341d8eef..1e1c1e3305 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -79,4 +79,4 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -Start-Process -FilePath "$powershellpath\$filename /quiet" -WindowStyle Hidden +Start-Process -FilePath $powershellpath\$filename -WindowStyle Hidden -Wait From 43236ca0ed0710d8b2a8539e9bebd3a27506cef1 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 13:32:43 -0500 Subject: [PATCH 084/107] Add basic tests for win_get_url and win_msi modules. --- examples/scripts/upgrade_to_ps3.ps1 | 2 +- .../roles/test_win_get_url/tasks/main.yml | 35 ++++++++++++++++ .../roles/test_win_msi/tasks/main.yml | 41 +++++++++++++++++++ test/integration/test_winrm.yml | 2 + 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 test/integration/roles/test_win_get_url/tasks/main.yml create mode 100644 test/integration/roles/test_win_msi/tasks/main.yml diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index 1e1c1e3305..d7909e1596 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -79,4 +79,4 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -Start-Process -FilePath $powershellpath\$filename -WindowStyle Hidden -Wait +Start-Process -FilePath $powershellpath\$filename -WindowStyle Hidden diff --git a/test/integration/roles/test_win_get_url/tasks/main.yml b/test/integration/roles/test_win_get_url/tasks/main.yml new file mode 100644 index 0000000000..26fb334c95 --- /dev/null +++ b/test/integration/roles/test_win_get_url/tasks/main.yml @@ -0,0 +1,35 @@ +# test code for the win_get_url module +# (c) 2014, Chris Church + +# 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 . + +- name: remove test file if it exists + raw: PowerShell -Command {Remove-Item "C:\Users\Administrator\win_get_url.jpg" -Force} + +- name: test win_get_url module + win_get_url: url=http://placehold.it/10x10.jpg dest='C:\Users\Administrator\win_get_url.jpg' + register: win_get_url_result + +- name: check win_get_url result + assert: + that: + - "not win_get_url_result|failed" + - "win_get_url_result|changed" + +# FIXME: +# - Test invalid url +# - Test invalid dest, when dest is directory +# - Test idempotence when downloading same url/dest (not yet implemented) diff --git a/test/integration/roles/test_win_msi/tasks/main.yml b/test/integration/roles/test_win_msi/tasks/main.yml new file mode 100644 index 0000000000..d0d7034d78 --- /dev/null +++ b/test/integration/roles/test_win_msi/tasks/main.yml @@ -0,0 +1,41 @@ +# test code for the win_msi module +# (c) 2014, Chris Church + +# 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 . + +- name: use win_get_url module to download msi + win_get_url: url=http://downloads.sourceforge.net/project/sevenzip/7-Zip/9.22/7z922-x64.msi dest='C:\7z922-x64.msi' + register: win_get_url_result + +- name: install 7zip msi + win_msi: path="{{ win_get_url_result.win_get_url.dest }}" + register: win_msi_install_result + +- name: check win_msi install result + assert: + that: + - "not win_msi_install_result|failed" + - "win_msi_install_result|changed" + +- name: uninstall 7zip msi + win_msi: path="{{ win_get_url_result.win_get_url.dest }}" state=absent + register: win_msi_uninstall_result + +- name: check win_msi uninstall result + assert: + that: + - "not win_msi_uninstall_result|failed" + - "win_msi_uninstall_result|changed" diff --git a/test/integration/test_winrm.yml b/test/integration/test_winrm.yml index 81443d4dbf..f8e205f0e2 100644 --- a/test/integration/test_winrm.yml +++ b/test/integration/test_winrm.yml @@ -10,3 +10,5 @@ - { role: test_win_slurp, tags: test_win_slurp } - { role: test_win_fetch, tags: test_win_fetch } - { role: test_win_stat, tags: test_win_stat } + - { role: test_win_get_url, tags: test_win_get_url } + - { role: test_win_msi, tags: test_win_msi } From 2654f7b2003dade99ae81dc1461c7afe1008f27e Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 13:36:09 -0500 Subject: [PATCH 085/107] Add copyright header to main winrm test playbook. --- examples/scripts/upgrade_to_ps3.ps1 | 2 +- test/integration/test_winrm.yml | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index d7909e1596..73f18a105f 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -79,4 +79,4 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -Start-Process -FilePath $powershellpath\$filename -WindowStyle Hidden +Start-Process -FilePath "$powershell\$filename" -ArgumentList /quiet diff --git a/test/integration/test_winrm.yml b/test/integration/test_winrm.yml index f8e205f0e2..0398174b2c 100644 --- a/test/integration/test_winrm.yml +++ b/test/integration/test_winrm.yml @@ -1,4 +1,20 @@ ---- +# test code powershell modules and winrm connection plugin +# (c) 2014, Chris Church + +# 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 . - hosts: windows gather_facts: false From 575a1fe19b8b7385262f84b6c7eb750287fd2213 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 19 Jun 2014 13:47:43 -0500 Subject: [PATCH 086/107] Clean up some additional formatting in intro_windows.rst --- docsite/rst/intro_windows.rst | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 9032a9df38..6829a24c1c 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -31,23 +31,23 @@ On a Linux control machine:: Inventory ````````` -Ansible's windows support relies on a few standard variables to indicate the username, password, and connection type (windows) of the remote hosts. These variables are most easily set up in inventory. This is used instead of SSH-keys or passwords as normally fed into Ansible. +Ansible's windows support relies on a few standard variables to indicate the username, password, and connection type (windows) of the remote hosts. These variables are most easily set up in inventory. This is used instead of SSH-keys or passwords as normally fed into Ansible:: [windows] winserver1.example.com - winserver2.example.com + winserver2.example.com In group_vars/windows.yml, define the following inventory variables:: ansible-vault edit group_vars/windows.yml - ansible_ssh_user: Administrator + ansible_ssh_user: Administrator ansible_ssh_pass: SekritPasswordGoesHere ansible_ssh_port: 5986 ansible_connection: winrm Notice that the ssh_port is not actually for SSH, but this is a holdover from how Ansible is mostly an SSH-oriented system. Again, Windows management will not happen over SSH. - + When using your playbook, don't forget to specify --ask-vault-pass to provide the password to unlock the file. Test your configuration like so, by trying to contact your Windows nodes. Note this is not an ICMP ping, but tests the Ansible @@ -79,12 +79,12 @@ In the powershell session, run the following to enable PS Remoting and set the e If your Windows firewall is enabled, you must also run the following command to allow firewall access to the public firewall profile: - .. code-block:: bash +.. code-block:: bash - # Windows 2012 / 2012R2 + # Windows 2012 / 2012R2 $ Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" -RemoteAddress Any - # Windows 2008 / 2008R2 + # Windows 2008 / 2008R2 $ NetSH ADVFirewall Set AllProfiles Settings remotemanagement Enable By default, Powershell remoting enables an HTTP listener. The following commands enable an HTTPS listener, which secures communication between the Control Machine and windows. @@ -99,16 +99,17 @@ Alternatively, a self-signed SSL certificate can be generated in powershell usin # Create the https listener $ winrm create winrm/config/Listener?Address=*+Transport=HTTPS  @{Hostname="host_name";CertificateThumbprint="certificate_thumbprint"} + # Delete the http listener $ WinRM delete winrm/config/listener?Address=*+Transport=HTTP Again, if your Windows firewall is enabled, the following command to allow firewall access to the HTTPS listener: - .. code-block:: bash +.. code-block:: bash - # Windows 2008 / 2008R2 / 2012 / 2012R2 + # Windows 2008 / 2008R2 / 2012 / 2012R2 $ netsh advfirewall firewall add rule name="Allow WinRM HTTPS" dir=in localport=5986 protocol=TCP action=allow - + It's time to verify things are working:: ansible windows [-i inventory] -m ping --ask-vault-pass @@ -124,7 +125,7 @@ Getting to Powershell 3.0 or higher on Remote Systems Additionally, Powershell 3.0 or higher is needed for most modules. You can actually use a minimal ansible example playbook to upgrade your windows systems from Powershell 2.0 to 3.0 in order to take -advantage of the *other* ansible modules. +advantage of the *other* ansible modules. Looking at an ansible checkout, copy the examples/scripts/upgrade_to_ps3.ps1 script from the repo into your local directory, and run a playbook that looks like the following:: @@ -171,10 +172,11 @@ Windows modules live in a "windows/" subfolder in the Ansible "library/" subtree Modules (ps1 files) should start as follows:: #!powershell + # + # WANT_JSON # POWERSHELL_COMMON - # # code goes here, reading in stdin as JSON and outputting JSON The above magic is neccessary to tell Ansible to mix in some common code and also know how to push modules out. The common code contains some nice wrappers around working with hash data structures and emitting JSON results, and possibly a few mpmore useful things. Regular Ansible has this same concept for reusing Python code - this is just the windows equivalent. @@ -189,7 +191,7 @@ You Must Have a Linux Control Machine Note running Ansible from a Windows control machine is NOT a goal of the project. Refrain from asking for this feature, as it limits what technologies, features, and code we can use in the main project in the future. A Linux control machine will be required to manage Windows hosts. - + Cygwin is not supported, so please do not ask questions about Ansible running from Cygwin. .. _windows_facts: @@ -240,11 +242,11 @@ And for a final example, here's how to use the win_stat module to test for file - debug: var=stat_file - name: check stat_file result - assert: - that: + assert: + that: - "stat_file.stat.exists" - "not stat_file.stat.isdir" - - "stat_file.stat.size > 0" + - "stat_file.stat.size > 0" - "stat_file.stat.md5" Again, recall that the Windows modules are all listed in the Windows category of modules, with the exception that the "raw", "script", and "fetch" modules are also available. These modules do not start with a "win" prefix. From adc4e70c75ca6d22c20ebefe464ef29c9d300ed0 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 19 Jun 2014 13:48:23 -0500 Subject: [PATCH 087/107] Make sure the module name is specified correctly in the win_feature docs --- library/windows/win_feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/windows/win_feature b/library/windows/win_feature index 6c64c06848..ad54ce7f09 100644 --- a/library/windows/win_feature +++ b/library/windows/win_feature @@ -1,6 +1,6 @@ DOCUMENTATION = ''' --- -module: win_get_url +module: win_feature version_added: "1.7" short_description: Fetches a file from a given URL description: From 2316b7785cbdb7b88cf623bd094a046ee0aee4c6 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 19 Jun 2014 13:48:45 -0500 Subject: [PATCH 088/107] Make sure the doc stubs for windows modules have proper license headers --- examples/scripts/upgrade_to_ps3.ps1 | 2 +- library/windows/win_feature | 23 +++++++++++++++++++++++ library/windows/win_get_url | 23 +++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/examples/scripts/upgrade_to_ps3.ps1 b/examples/scripts/upgrade_to_ps3.ps1 index 73f18a105f..fc924ccb64 100644 --- a/examples/scripts/upgrade_to_ps3.ps1 +++ b/examples/scripts/upgrade_to_ps3.ps1 @@ -79,4 +79,4 @@ else $FileName = $DownLoadUrl.Split('/')[-1] download-file $downloadurl "$powershellpath\$filename" -Start-Process -FilePath "$powershell\$filename" -ArgumentList /quiet +Start-Process -FilePath ".$powershellpath\$filename" -ArgumentList /quiet diff --git a/library/windows/win_feature b/library/windows/win_feature index ad54ce7f09..0151ee3226 100644 --- a/library/windows/win_feature +++ b/library/windows/win_feature @@ -1,3 +1,26 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Paul Durivage , and others +# +# 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 . + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + DOCUMENTATION = ''' --- module: win_feature diff --git a/library/windows/win_get_url b/library/windows/win_get_url index 93f41acb7e..10910cf605 100644 --- a/library/windows/win_get_url +++ b/library/windows/win_get_url @@ -1,3 +1,26 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Paul Durivage , and others +# +# 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 . + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + DOCUMENTATION = ''' --- module: win_get_url From cd280440068fe37e68104ca1ed21e02893c1234a Mon Sep 17 00:00:00 2001 From: Craig Ackerman Date: Thu, 19 Jun 2014 19:40:31 +0000 Subject: [PATCH 089/107] Specified https firewall rule to only use public profile --- docsite/rst/intro_windows.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 6829a24c1c..7389e0a4fa 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -108,7 +108,7 @@ Again, if your Windows firewall is enabled, the following command to allow firew .. code-block:: bash # Windows 2008 / 2008R2 / 2012 / 2012R2 - $ netsh advfirewall firewall add rule name="Allow WinRM HTTPS" dir=in localport=5986 protocol=TCP action=allow + $ netsh advfirewall firewall add rule Profile=public name="Allow WinRM HTTPS" dir=in localport=5986 protocol=TCP action=allow It's time to verify things are working:: From 4e738e6fa97c60425c0ab76ce9ab9575ab420d15 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 15:37:35 -0500 Subject: [PATCH 090/107] Optimize buffer size for put_file. --- .../runner/connection_plugins/winrm.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/ansible/runner/connection_plugins/winrm.py b/lib/ansible/runner/connection_plugins/winrm.py index b9c1f3be8e..6353d9f4c8 100644 --- a/lib/ansible/runner/connection_plugins/winrm.py +++ b/lib/ansible/runner/connection_plugins/winrm.py @@ -101,7 +101,7 @@ class Connection(object): if exc: raise exc - def _winrm_exec(self, command, args, from_exec=False): + def _winrm_exec(self, command, args=(), from_exec=False): if from_exec: vvvv("WINRM EXEC %r %r" % (command, args), host=self.host) else: @@ -154,9 +154,23 @@ class Connection(object): vvv("PUT %s TO %s" % (in_path, out_path), host=self.host) if not os.path.exists(in_path): raise errors.AnsibleFileNotFound("file or module does not exist: %s" % in_path) - buffer_size = 1024 # FIXME: Find max size or optimize. with open(in_path) as in_file: in_size = os.path.getsize(in_path) + script_template = ''' + $s = [System.IO.File]::OpenWrite("%s"); + $s.Seek(%d, [System.IO.SeekOrigin]::Begin) | Out-Null; + $b = [System.Convert]::FromBase64String("%s"); + $s.Write($b, 0, $b.length) | Out-Null; + $s.SetLength(%d) | Out-Null; + $s.Close() | Out-Null; + ''' + # Determine max size of data we can pass per command. + script = script_template % (powershell._escape(out_path), in_size, '', in_size) + cmd = powershell._encode_script(script) + # Encode script with no data, subtract its length from 8190 (max + # windows command length), divide by 2.67 (UTF16LE base64 command + # encoding), then by 1.35 again (data base64 encoding). + buffer_size = int(((8190 - len(cmd)) / 2.67) / 1.35) for offset in xrange(0, in_size, buffer_size): try: out_data = in_file.read(buffer_size) @@ -164,16 +178,7 @@ class Connection(object): if out_data.lower().startswith('#!powershell') and not out_path.lower().endswith('.ps1'): out_path = out_path + '.ps1' b64_data = base64.b64encode(out_data) - script = ''' - $bufferSize = %d; - $stream = [System.IO.File]::OpenWrite("%s"); - $stream.Seek(%d, [System.IO.SeekOrigin]::Begin) | Out-Null; - $data = "%s"; - $buffer = [System.Convert]::FromBase64String($data); - $stream.Write($buffer, 0, $buffer.length) | Out-Null; - $stream.SetLength(%d) | Out-Null; - $stream.Close() | Out-Null; - ''' % (buffer_size, powershell._escape(out_path), offset, b64_data, in_size) + script = script_template % (powershell._escape(out_path), offset, b64_data, in_size) vvvv("WINRM PUT %s to %s (offset=%d size=%d)" % (in_path, out_path, offset, len(out_data)), host=self.host) cmd_parts = powershell._encode_script(script, as_list=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) From 8816ebb3f848d8eacca20f8f7167f118c52f8f02 Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Thu, 19 Jun 2014 15:37:58 -0500 Subject: [PATCH 091/107] Add documentation for win_user module --- library/windows/win_user | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 library/windows/win_user diff --git a/library/windows/win_user b/library/windows/win_user new file mode 100644 index 0000000000..c161f8f04f --- /dev/null +++ b/library/windows/win_user @@ -0,0 +1,68 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Matt Martz , and others +# +# 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 . + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +DOCUMENTATION = ''' +--- +module: win_user +version_added: "1.7" +short_description: Manages local Windows user accounts +description: + - Manages local Windows user accounts +options: + name: + description: + - Username of the user to manage + required: true + default: null + aliases: [] + password: + description: + - Password for the user + required: true + default: null + aliases: [] + state: + description: + - Whether to create or delete a user + required: false + choices: + - present + - absent + default: present + aliases: [] +author: Paul Durivage +''' + +EXAMPLES = ''' +# Playbook example +--- +- name: Add a user + hosts: all + gather_facts: false + tasks: + - name: Add User + win_user: + name: ansible + password: "@ns1bl3" + state: present +''' From 8a121fd6ae378106b9b59318c3bc44a95d9e3ea7 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 16:19:23 -0500 Subject: [PATCH 092/107] Squeeze a few more bytes out of put_file script. --- lib/ansible/runner/connection_plugins/winrm.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ansible/runner/connection_plugins/winrm.py b/lib/ansible/runner/connection_plugins/winrm.py index 6353d9f4c8..e9ee8692f6 100644 --- a/lib/ansible/runner/connection_plugins/winrm.py +++ b/lib/ansible/runner/connection_plugins/winrm.py @@ -158,11 +158,11 @@ class Connection(object): in_size = os.path.getsize(in_path) script_template = ''' $s = [System.IO.File]::OpenWrite("%s"); - $s.Seek(%d, [System.IO.SeekOrigin]::Begin) | Out-Null; + [void]$s.Seek(%d, [System.IO.SeekOrigin]::Begin); $b = [System.Convert]::FromBase64String("%s"); - $s.Write($b, 0, $b.length) | Out-Null; - $s.SetLength(%d) | Out-Null; - $s.Close() | Out-Null; + [void]$s.Write($b, 0, $b.length); + [void]$s.SetLength(%d); + [void]$s.Close(); ''' # Determine max size of data we can pass per command. script = script_template % (powershell._escape(out_path), in_size, '', in_size) From dd3f7c2dab83323b1357b0bb4eb35cdac3303fab Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 16:20:09 -0500 Subject: [PATCH 093/107] Fix trailing slash on returned temp path. --- lib/ansible/runner/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 9369a274c6..d8d9463028 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -1049,7 +1049,7 @@ class Runner(object): output = output + ": %s" % result['stdout'] raise errors.AnsibleError(output) - rc = utils.last_non_blank_line(result['stdout']).strip() + '/' + rc = conn.shell.join_path(utils.last_non_blank_line(result['stdout']).strip(), '') # Catch failure conditions, files should never be # written to locations in /. if rc == '/': From 556ff9b7f1c8515332b906868565c5097978b11d Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 15:42:07 -0500 Subject: [PATCH 094/107] Refactor of win_user module --- library/windows/win_user.ps1 | 105 +++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/library/windows/win_user.ps1 b/library/windows/win_user.ps1 index a89b9f218e..dd8a7b4c28 100644 --- a/library/windows/win_user.ps1 +++ b/library/windows/win_user.ps1 @@ -1,8 +1,8 @@ #!powershell -# (c) 2014, Matt Martz , and others -# # This file is part of Ansible # +# Copyright 2014, Paul Durivage +# # 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 @@ -21,12 +21,13 @@ $params = Parse-Args $args; -$result = New-Object psobject; -Set-Attr $result "changed" $false; +$result = New-Object psobject @{ + changed = false +}; -If (-not $params.username.GetType) +If (-not $params.name.GetType) { - Fail-Json $result "missing required arguments: username" + Fail-Json $result "missing required arguments: name" } If (-not $params.password.GetType) @@ -34,43 +35,77 @@ If (-not $params.password.GetType) Fail-Json $result "missing required arguments: password" } -$extra_args = "" -If ($params.extra_args.GetType) -{ - $extra_args = $params.extra_args; +$extra_args = $params "extra_args" "" + +If ($params.state) { + $state = $params.state.ToString().ToLower() + If (($state -ne 'present') -and ($state -ne 'absent')) { + Fail-Json $result "state is '$state'; must be 'present' or 'absent'" + } +} +Elseif (!$params.state) { + $state = "present" } -If ($params.creates.GetType -and $params.state.GetType -and $params.state -ne "absent") -{ - If (Does-User-Exist $params.username) - { - Exit-Json $result; +$username = Get-Attr $params "name" +$password = Get-Attr $params "password" + +$user_obj = Get-User $username +if (-not $user_obj) { + Fail-Json $result "Could not find user: $username" +} + +if ($state -eq 'present') { + # Add or update user + try { + if ($user_obj) { + Update-Password $user_obj $password + } + else { + Create-User $username $password + } + $result.changed = $true + } + catch { + Fail-Json $result $_.Exception.Message + } + +} +else { + # Remove user + try { + Delete-User $bob + $result.changed = $true + } + catch { + Fail-Json $result $_.Exception.Message } } -$logfile = [IO.Path]::GetTempFileName(); -if ($params.state.GetType -and $params.state -eq "absent") -{ - NET USER $params.username $params.password /ADD +$adsi = [ADSI]"WinNT://$env:COMPUTERNAME" + +function Get-User($user) { + $adsi.Children | where {$_.SchemaClassName -eq 'user' -and $_.Name -eq $user } + return } -Set-Attr $result "changed" $true; +function Create-User([string]$user, [string]$passwd) { + $user = $adsi.Create("User", $user) + $user.SetPassword($passwd) + $user.SetInfo() + $user + return +} -$logcontents = Get-Content $logfile; -Remove-Item $logfile; +function Update-Password($user, [string]$passwd) { + $user.SetPassword($passwd) + $user.SetInfo() +} -Set-Attr $result "log" $logcontents; +function Delete-User($user) { + $adsi.delete("user", $user.Name.Value) +} + +Set-Attr $result "user" $user_obj; # Soemthing goes here. Exit-Json $result; - -Function Does-User-Exist($username) -{ -$objComputer = [ADSI]("WinNT://$env:COMPUTERNAME,computer") - -$colUsers = ($objComputer.psbase.children | - Where-Object {$_.psBase.schemaClassName -eq "User"} | - Select-Object -expand Name) - -$blnFound = $colUsers -eq $username - -} \ No newline at end of file From 29a308a2336b8de36e30743f925c6997e64ef706 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 16:23:38 -0500 Subject: [PATCH 095/107] Move functions to top for correct scope --- library/windows/win_user.ps1 | 55 ++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/library/windows/win_user.ps1 b/library/windows/win_user.ps1 index dd8a7b4c28..6ce9d02368 100644 --- a/library/windows/win_user.ps1 +++ b/library/windows/win_user.ps1 @@ -19,6 +19,32 @@ # WANT_JSON # POWERSHELL_COMMON +######## +$adsi = [ADSI]"WinNT://$env:COMPUTERNAME" + +function Get-User($user) { + $adsi.Children | where {$_.SchemaClassName -eq 'user' -and $_.Name -eq $user } + return +} + +function Create-User([string]$user, [string]$passwd) { + $user = $adsi.Create("User", $user) + $user.SetPassword($passwd) + $user.SetInfo() + $user + return +} + +function Update-Password($user, [string]$passwd) { + $user.SetPassword($passwd) + $user.SetInfo() +} + +function Delete-User($user) { + $adsi.delete("user", $user.Name.Value) +} +######## + $params = Parse-Args $args; $result = New-Object psobject @{ @@ -82,30 +108,9 @@ else { } } -$adsi = [ADSI]"WinNT://$env:COMPUTERNAME" - -function Get-User($user) { - $adsi.Children | where {$_.SchemaClassName -eq 'user' -and $_.Name -eq $user } - return -} - -function Create-User([string]$user, [string]$passwd) { - $user = $adsi.Create("User", $user) - $user.SetPassword($passwd) - $user.SetInfo() - $user - return -} - -function Update-Password($user, [string]$passwd) { - $user.SetPassword($passwd) - $user.SetInfo() -} - -function Delete-User($user) { - $adsi.delete("user", $user.Name.Value) -} - -Set-Attr $result "user" $user_obj; # Soemthing goes here. +# Set-Attr $result "user" $user_obj +Set-Attr $result "user_name" $user_obj.Name +Set-Attr $result "user_fullname" $user_obj.FullName +Set-Attr $result "user_path" $user_obj.Path Exit-Json $result; From 03f69ac6c877f2877b041ecf70570733297a2847 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 16:24:12 -0500 Subject: [PATCH 096/107] Fix incorrect name --- library/windows/win_user.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/windows/win_user.ps1 b/library/windows/win_user.ps1 index 6ce9d02368..a6f1971cd7 100644 --- a/library/windows/win_user.ps1 +++ b/library/windows/win_user.ps1 @@ -48,7 +48,7 @@ function Delete-User($user) { $params = Parse-Args $args; $result = New-Object psobject @{ - changed = false + changed = $false }; If (-not $params.name.GetType) From 08c0b7e66ec734ec1142825181ea8dc46bb5c2df Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 16:24:27 -0500 Subject: [PATCH 097/107] Remove commented out lines --- library/windows/win_user.ps1 | 5 ----- 1 file changed, 5 deletions(-) diff --git a/library/windows/win_user.ps1 b/library/windows/win_user.ps1 index a6f1971cd7..003579104d 100644 --- a/library/windows/win_user.ps1 +++ b/library/windows/win_user.ps1 @@ -61,8 +61,6 @@ If (-not $params.password.GetType) Fail-Json $result "missing required arguments: password" } -$extra_args = $params "extra_args" "" - If ($params.state) { $state = $params.state.ToString().ToLower() If (($state -ne 'present') -and ($state -ne 'absent')) { @@ -77,9 +75,6 @@ $username = Get-Attr $params "name" $password = Get-Attr $params "password" $user_obj = Get-User $username -if (-not $user_obj) { - Fail-Json $result "Could not find user: $username" -} if ($state -eq 'present') { # Add or update user From 6dd94347072f8e2bf916bc0c5413b729e0bee4a6 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 16:24:57 -0500 Subject: [PATCH 098/107] Fix so it only deletes users that exist --- library/windows/win_user.ps1 | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/windows/win_user.ps1 b/library/windows/win_user.ps1 index 003579104d..1b7bec26f0 100644 --- a/library/windows/win_user.ps1 +++ b/library/windows/win_user.ps1 @@ -86,17 +86,22 @@ if ($state -eq 'present') { Create-User $username $password } $result.changed = $true + $user_obj = Get-User $username } catch { Fail-Json $result $_.Exception.Message } - } else { # Remove user try { - Delete-User $bob - $result.changed = $true + if ($user_obj.GetType) { + Delete-User $user_obj + $result.changed = $true + } + else { + Set-Attr $result "msg" "User '$username' was not found" + } } catch { Fail-Json $result $_.Exception.Message From 08dc72b5e70800dbad85be9ae12f747853578d4e Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 16:40:18 -0500 Subject: [PATCH 099/107] Update documentation --- library/windows/win_user | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/windows/win_user b/library/windows/win_user index c161f8f04f..e2da6a1ddb 100644 --- a/library/windows/win_user +++ b/library/windows/win_user @@ -37,7 +37,7 @@ options: aliases: [] password: description: - - Password for the user + - Password for the user (plain text) required: true default: null aliases: [] @@ -54,6 +54,10 @@ author: Paul Durivage ''' EXAMPLES = ''' +# Ad-hoc example +$ ansible -i hosts -m win_user -a "name=bob password=Password12345" all +$ ansible -i hosts -m win_user -a "name=bob password=Password12345 state=absent" all + # Playbook example --- - name: Add a user @@ -64,5 +68,4 @@ EXAMPLES = ''' win_user: name: ansible password: "@ns1bl3" - state: present ''' From 07267659b00cca7d9d3e825635941c90deb459bc Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 16:40:35 -0500 Subject: [PATCH 100/107] Rename variable --- library/windows/win_user.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/windows/win_user.ps1 b/library/windows/win_user.ps1 index 1b7bec26f0..5fbb5cca27 100644 --- a/library/windows/win_user.ps1 +++ b/library/windows/win_user.ps1 @@ -28,10 +28,10 @@ function Get-User($user) { } function Create-User([string]$user, [string]$passwd) { - $user = $adsi.Create("User", $user) - $user.SetPassword($passwd) - $user.SetInfo() - $user + $adsiuser = $adsi.Create("User", $user) + $adsiuser.SetPassword($passwd) + $adsiuser.SetInfo() + $adsiuser return } From 319f32e408f0b3108826f073c85db8b1215da081 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 16:41:05 -0500 Subject: [PATCH 101/107] Move check down for access to --- library/windows/win_user.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/windows/win_user.ps1 b/library/windows/win_user.ps1 index 5fbb5cca27..25267b631b 100644 --- a/library/windows/win_user.ps1 +++ b/library/windows/win_user.ps1 @@ -56,11 +56,6 @@ If (-not $params.name.GetType) Fail-Json $result "missing required arguments: name" } -If (-not $params.password.GetType) -{ - Fail-Json $result "missing required arguments: password" -} - If ($params.state) { $state = $params.state.ToString().ToLower() If (($state -ne 'present') -and ($state -ne 'absent')) { @@ -71,6 +66,11 @@ Elseif (!$params.state) { $state = "present" } +If ((-not $params.password.GetType) -and ($state -eq 'present')) +{ + Fail-Json $result "missing required arguments: password" +} + $username = Get-Attr $params "name" $password = Get-Attr $params "password" From e6cd216adee9b406c6bfd400adc14177aa619774 Mon Sep 17 00:00:00 2001 From: Paul Durivage Date: Thu, 19 Jun 2014 16:41:24 -0500 Subject: [PATCH 102/107] Check .GetType rather than object directly --- library/windows/win_user.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/windows/win_user.ps1 b/library/windows/win_user.ps1 index 25267b631b..306d7a0db2 100644 --- a/library/windows/win_user.ps1 +++ b/library/windows/win_user.ps1 @@ -79,7 +79,7 @@ $user_obj = Get-User $username if ($state -eq 'present') { # Add or update user try { - if ($user_obj) { + if ($user_obj.GetType) { Update-Password $user_obj $password } else { From 5f13af97b8804ca40ee2f230cd29e732a92ccbcd Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 19 Jun 2014 16:57:03 -0500 Subject: [PATCH 103/107] Remove part about powershell upgrade (for now). --- docsite/rst/intro_windows.rst | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 7389e0a4fa..cb66573bb0 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -120,23 +120,15 @@ bootstrapping! .. _getting_to_powershell_three_or_higher: -Getting to Powershell 3.0 or higher on Remote Systems -`````````````````````````````````````````````````````` +Getting to Powershell 3.0 or higher +``````````````````````````````````` -Additionally, Powershell 3.0 or higher is needed for most modules. You can actually use a minimal -ansible example playbook to upgrade your windows systems from Powershell 2.0 to 3.0 in order to take -advantage of the *other* ansible modules. +Powershell 3.0 or higher is needed for most modules. -Looking at an ansible checkout, copy the examples/scripts/upgrade_to_ps3.ps1 script from the repo into -your local directory, and run a playbook that looks like the following:: +Looking at an ansible checkout, copy the examples/scripts/upgrade_to_ps3.ps1 script onto the remote host +and run a powershell console as an administrator: - - hosts: windows - gather_facts: no - tasks: - - script: upgrade_to_ps3.ps1 - -The hosts in the above group will then be running a new enough version of Powershell to be managed -by the full compliment of Ansible modules. + ./upgrade_to_ps3.ps1 .. _what_windows_modules_are_available: From 7b33bc9fea11a0c209d86aa266a351d56c41a68a Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 19 Jun 2014 21:17:47 -0500 Subject: [PATCH 104/107] Windows docs tweaks. --- docsite/rst/intro_windows.rst | 38 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index cb66573bb0..77e2af27d8 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -13,10 +13,12 @@ As you may have already read, Ansible manages Linux/Unix machines using SSH by d Starting in version 1.7, Ansible also contains support for managing Windows machines. This uses native powershell remoting, rather than SSH. -Ansible will still be run from a Linux guest, and uses the "winrm" Python module to talk to remote hosts. +Ansible will still be run from a Linux control machine, and uses the "winrm" Python module to talk to remote hosts. No additional software needs to be installed on the remote machines for Ansible to manage them, it still maintains the agentless properties that make it popular on Linux/Unix. +Note that it is expected you have a basic understanding of Ansible prior to jumping into this section, so if you haven't written a Linux playbook first, it might be worthwhile to dig in there first. + .. _windows_installing: Installing on the Control Machine @@ -39,14 +41,15 @@ Ansible's windows support relies on a few standard variables to indicate the use In group_vars/windows.yml, define the following inventory variables:: - ansible-vault edit group_vars/windows.yml + # it is suggested that these be encrypted with ansible-vault: + # ansible-vault edit group_vars/windows.yml ansible_ssh_user: Administrator ansible_ssh_pass: SekritPasswordGoesHere ansible_ssh_port: 5986 ansible_connection: winrm -Notice that the ssh_port is not actually for SSH, but this is a holdover from how Ansible is mostly an SSH-oriented system. Again, Windows management will not happen over SSH. +Notice that the ssh_port is not actually for SSH, but this is a holdover variable name from how Ansible is mostly an SSH-oriented system. Again, Windows management will not happen over SSH. When using your playbook, don't forget to specify --ask-vault-pass to provide the password to unlock the file. @@ -89,11 +92,11 @@ If your Windows firewall is enabled, you must also run the following command to By default, Powershell remoting enables an HTTP listener. The following commands enable an HTTPS listener, which secures communication between the Control Machine and windows. -An SSL certificate for server authentication is required to create the HTTPS listener. The existence of an existing certificate in the computer account can be verified by using the MMC snap-in, as documented ' +An SSL certificate for server authentication is required to create the HTTPS listener. The existence of an existing certificate in the computer account can be verified by using the MMC snap-in. A best practice for SSL certificates is generating them from an internal or external certificate authority. An existing certificate could be located in the computer account certificate store `using the following article `_. -Alternatively, a self-signed SSL certificate can be generated in powershell using 'the following technet article '. At a minimum, the subject name should match the hostname, and Server Authentication is required. Once the self signed certificate is obtained, the certificate thumbprint can be identified using `How to: Retrieve the Thumbprint of a Certificate `_ +Alternatively, a self-signed SSL certificate can be generated in powershell using `the following technet article `_. At a minimum, the subject name should match the hostname, and Server Authentication is required. Once the self signed certificate is obtained, the certificate thumbprint can be identified using `How to: Retrieve the Thumbprint of a Certificate `_. .. code-block:: bash @@ -115,19 +118,18 @@ It's time to verify things are working:: ansible windows [-i inventory] -m ping --ask-vault-pass However, if you are still running Powershell 2.0 on remote systems, it's time to use Ansible to upgrade powershell -before proceeding further, as some of the Ansible modules will require Powershell 3.0. Thankfully it's self -bootstrapping! +before proceeding further, as some of the Ansible modules will require Powershell 3.0. + +In the future, Ansible may provide a shortcut installer that automates these steps for prepping a Windows machine. .. _getting_to_powershell_three_or_higher: Getting to Powershell 3.0 or higher ``````````````````````````````````` -Powershell 3.0 or higher is needed for most modules. - -Looking at an ansible checkout, copy the examples/scripts/upgrade_to_ps3.ps1 script onto the remote host -and run a powershell console as an administrator: +Powershell 3.0 or higher is needed for most provided Ansible modules for Windows. +Looking at an ansible checkout, copy the `examples/scripts/upgrade_to_ps3.ps1 `_ script onto the remote host and run a powershell console as an administrator:: ./upgrade_to_ps3.ps1 .. _what_windows_modules_are_available: @@ -136,24 +138,26 @@ What modules are available `````````````````````````` Most of the Ansible modules in core Ansible are written for a combination of Linux/Unix machines and arbitrary web services, though there are various -Windows modules as listed in the "windows" subcategory of the Ansible module index. +Windows modules as listed in the `"windows" subcategory of the Ansible module index `_. Browse this index to see what is available. In many cases, it may not be neccessary to even write or use an Ansible module. -In particular, the "win_script" module can be used to run arbitrary powershell scripts, allowing Windows administrators familiar with powershell a very native way to do things, as in the following playbook:: +In particular, the "script" module can be used to run arbitrary powershell scripts, allowing Windows administrators familiar with powershell a very native way to do things, as in the following playbook:: - hosts: windows tasks: - - win_script: foo.ps1 --argument --other-argument + - script: foo.ps1 --argument --other-argument + +Note there are a few other Ansible modules that don't start with "win" that also function, including "slurp", "raw", and "setup" (which is how fact gathering works). .. _developers_developers_developers: Developers: Supported modules and how it works `````````````````````````````````````````````` -Developing ansible modules are covered in a later section of the documentation, with a focus on Linux/Unix. +Developing ansible modules are covered in a `later section of the documentation `_, with a focus on Linux/Unix. What if you want to write Windows modules for ansible though? For Windows, ansible modules are implemented in Powershell. Skim those Linux/Unix module development chapters before proceeding. @@ -177,8 +181,8 @@ What modules you see in windows/ are just a start. Additional modules may be su .. _windows_and_linux_control_machine: -You Must Have a Linux Control Machine -````````````````````````````````````` +Reminder: You Must Have a Linux Control Machine +``````````````````````````````````````````````` Note running Ansible from a Windows control machine is NOT a goal of the project. Refrain from asking for this feature, as it limits what technologies, features, and code we can use in the main project in the future. A Linux control machine From 1663faa7a14c4242b139047eeb6eed7cc6ae8b37 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 19 Jun 2014 21:22:48 -0500 Subject: [PATCH 105/107] update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 784423b9ca..d926be4a7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Ansible Changes By Release ## 1.7 "Summer Nights" - Active Development +Major new features: + +* Windows support (alpha) using native PowerShell remoting + New inventory scripts: * SoftLayer @@ -14,6 +18,13 @@ New Modules: * cloud: rax_meta * cloud: rax_scaling_group * cloud: rax_scaling_policy +* windows: version of setup module +* windows: version of slurp module +* windows: win_feature +* windows: win_get_url +* windows: win_msi +* windows: win_ping +* windows: win_user ## 1.6.3 "And the Cradle Will Rock" - Jun 09, 2014 From ffaf442037ad74bbe4cc2a3d2327db66998046da Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Thu, 19 Jun 2014 21:35:38 -0500 Subject: [PATCH 106/107] ping is win_ping, so doc fix. --- docsite/rst/intro_windows.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docsite/rst/intro_windows.rst b/docsite/rst/intro_windows.rst index 77e2af27d8..199700c155 100644 --- a/docsite/rst/intro_windows.rst +++ b/docsite/rst/intro_windows.rst @@ -56,7 +56,7 @@ When using your playbook, don't forget to specify --ask-vault-pass to provide th Test your configuration like so, by trying to contact your Windows nodes. Note this is not an ICMP ping, but tests the Ansible communication channel that leverages Windows remoting:: - ansible windows [-i inventory] -m ping --ask-vault-pass + ansible windows [-i inventory] -m win_ping --ask-vault-pass If you haven't done anything to prep your systems yet, this won't work yet. This is covered in a later section about how to enable powershell remoting - and if neccessary - how to upgrade powershell to @@ -115,7 +115,7 @@ Again, if your Windows firewall is enabled, the following command to allow firew It's time to verify things are working:: - ansible windows [-i inventory] -m ping --ask-vault-pass + ansible windows [-i inventory] -m win_ping --ask-vault-pass However, if you are still running Powershell 2.0 on remote systems, it's time to use Ansible to upgrade powershell before proceeding further, as some of the Ansible modules will require Powershell 3.0. @@ -129,8 +129,7 @@ Getting to Powershell 3.0 or higher Powershell 3.0 or higher is needed for most provided Ansible modules for Windows. -Looking at an ansible checkout, copy the `examples/scripts/upgrade_to_ps3.ps1 `_ script onto the remote host and run a powershell console as an administrator:: - ./upgrade_to_ps3.ps1 +Looking at an ansible checkout, copy the `examples/scripts/upgrade_to_ps3.ps1 `_ script onto the remote host and run a powershell console as an administrator. You will now be running Powershell 3 and can try connectivity again using the win_ping technique referenced above. .. _what_windows_modules_are_available: From 615f70e3f4817e98b97a78402e2b049d72f4ec40 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Thu, 19 Jun 2014 21:54:21 -0500 Subject: [PATCH 107/107] Fix missing space in script action plugin. --- lib/ansible/runner/action_plugins/script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/runner/action_plugins/script.py b/lib/ansible/runner/action_plugins/script.py index e26da444b9..593a42d2f4 100644 --- a/lib/ansible/runner/action_plugins/script.py +++ b/lib/ansible/runner/action_plugins/script.py @@ -123,7 +123,7 @@ class ActionModule(object): # add preparation steps to one ssh roundtrip executing the script env_string = self.runner._compute_environment_string(conn, inject) - module_args = env_string + tmp_src + ' ' + args + module_args = ' '.join([env_string, tmp_src, args]) handler = utils.plugins.action_loader.get('raw', self.runner) result = handler.run(conn, tmp, 'raw', module_args, inject)