2014-11-14 22:14:08 +00:00
# (c) 2014, Chris Church <chris@ninemoreminutes.com>
#
# 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 <http://www.gnu.org/licenses/>.
2015-04-23 23:54:48 +00:00
2015-04-27 15:46:26 +00:00
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
2014-11-14 22:14:08 +00:00
import base64
2015-08-23 05:54:46 +00:00
import inspect
2014-11-14 22:14:08 +00:00
import os
import re
import shlex
import traceback
2015-10-23 21:37:50 +00:00
import json
import xmltodict
2014-11-14 22:14:08 +00:00
2015-10-16 00:55:23 +00:00
from ansible . compat . six . moves . urllib . parse import urlunsplit
2015-09-17 14:44:26 +00:00
2016-04-25 18:20:27 +00:00
from ansible . errors import AnsibleError , AnsibleConnectionFailure
2014-11-14 22:14:08 +00:00
try :
2016-04-25 18:20:27 +00:00
import winrm
2014-11-14 22:14:08 +00:00
from winrm import Response
from winrm . protocol import Protocol
except ImportError :
2015-04-27 05:28:25 +00:00
raise AnsibleError ( " winrm is not installed " )
2014-11-14 22:14:08 +00:00
2015-04-23 23:54:48 +00:00
HAVE_KERBEROS = False
try :
import kerberos
HAVE_KERBEROS = True
except ImportError :
pass
2014-11-14 22:14:08 +00:00
2015-11-11 15:10:14 +00:00
from ansible . errors import AnsibleFileNotFound
2015-09-15 15:57:54 +00:00
from ansible . plugins . connection import ConnectionBase
2015-10-23 21:37:50 +00:00
from ansible . utils . hashing import secure_hash
2015-06-03 03:42:00 +00:00
from ansible . utils . path import makedirs_safe
2015-10-21 14:59:46 +00:00
from ansible . utils . unicode import to_bytes , to_unicode , to_str
2015-10-09 00:37:24 +00:00
from ansible . utils . vars import combine_vars
2014-11-14 22:14:08 +00:00
2015-11-11 15:10:14 +00:00
try :
from __main__ import display
except ImportError :
from ansible . utils . display import Display
display = Display ( )
2015-04-27 05:28:25 +00:00
class Connection ( ConnectionBase ) :
2014-11-14 22:14:08 +00:00
''' WinRM connections over HTTP/HTTPS. '''
2016-03-16 18:22:50 +00:00
transport = ' winrm '
2016-01-11 17:07:05 +00:00
module_implementation_preferences = ( ' .ps1 ' , ' .exe ' , ' ' )
2015-11-30 16:32:53 +00:00
become_methods = [ ]
2016-02-02 18:13:02 +00:00
allow_executable = False
2015-09-10 19:55:59 +00:00
2015-04-27 05:28:25 +00:00
def __init__ ( self , * args , * * kwargs ) :
self . has_pipelining = False
self . protocol = None
self . shell_id = None
self . delegate = None
2015-06-29 19:41:51 +00:00
self . _shell_type = ' powershell '
2014-11-14 22:14:08 +00:00
2016-04-25 18:20:27 +00:00
# FUTURE: Add runas support
2015-04-23 23:54:48 +00:00
2015-04-27 05:28:25 +00:00
super ( Connection , self ) . __init__ ( * args , * * kwargs )
2015-08-23 05:54:46 +00:00
def set_host_overrides ( self , host ) :
2014-11-14 22:14:08 +00:00
'''
2015-08-23 05:54:46 +00:00
Override WinRM - specific options from host variables .
2014-11-14 22:14:08 +00:00
'''
2015-10-09 00:37:24 +00:00
host_vars = combine_vars ( host . get_group_vars ( ) , host . get_vars ( ) )
2015-04-27 05:28:25 +00:00
2015-09-14 19:59:40 +00:00
self . _winrm_host = self . _play_context . remote_addr
self . _winrm_port = int ( self . _play_context . port or 5986 )
2015-08-23 05:54:46 +00:00
self . _winrm_scheme = host_vars . get ( ' ansible_winrm_scheme ' , ' http ' if self . _winrm_port == 5985 else ' https ' )
self . _winrm_path = host_vars . get ( ' ansible_winrm_path ' , ' /wsman ' )
2015-09-14 19:59:40 +00:00
self . _winrm_user = self . _play_context . remote_user
self . _winrm_pass = self . _play_context . password
2015-08-23 05:54:46 +00:00
2016-04-25 18:20:27 +00:00
if hasattr ( winrm , ' FEATURE_SUPPORTED_AUTHTYPES ' ) :
self . _winrm_supported_authtypes = set ( winrm . FEATURE_SUPPORTED_AUTHTYPES )
2015-08-23 05:54:46 +00:00
else :
2016-04-25 18:20:27 +00:00
# for legacy versions of pywinrm, use the values we know are supported
self . _winrm_supported_authtypes = set ( [ ' plaintext ' , ' ssl ' , ' kerberos ' ] )
2015-04-27 05:28:25 +00:00
2016-04-25 18:20:27 +00:00
# TODO: figure out what we want to do with auto-transport selection in the face of NTLM/Kerb/CredSSP/Cert/Basic
2015-10-09 00:37:24 +00:00
transport_selector = ' ssl ' if self . _winrm_scheme == ' https ' else ' plaintext '
2016-04-25 18:20:27 +00:00
if HAVE_KERBEROS and ( ( self . _winrm_user and ' @ ' in self . _winrm_user ) ) :
2015-10-09 00:37:24 +00:00
self . _winrm_transport = ' kerberos, %s ' % transport_selector
2015-08-23 05:54:46 +00:00
else :
2015-10-09 00:37:24 +00:00
self . _winrm_transport = transport_selector
2015-08-23 05:54:46 +00:00
self . _winrm_transport = host_vars . get ( ' ansible_winrm_transport ' , self . _winrm_transport )
if isinstance ( self . _winrm_transport , basestring ) :
self . _winrm_transport = [ x . strip ( ) for x in self . _winrm_transport . split ( ' , ' ) if x . strip ( ) ]
2015-04-27 05:28:25 +00:00
2016-04-25 18:20:27 +00:00
unsupported_transports = set ( self . _winrm_transport ) . difference ( self . _winrm_supported_authtypes )
if unsupported_transports :
raise AnsibleError ( ' The installed version of WinRM does not support transport(s) %s ' % list ( unsupported_transports ) )
self . _winrm_kwargs = dict ( username = self . _winrm_user , password = self . _winrm_pass )
2015-08-23 05:54:46 +00:00
argspec = inspect . getargspec ( Protocol . __init__ )
2016-04-25 18:20:27 +00:00
supported_winrm_args = set ( argspec . args )
passed_winrm_args = set ( [ v . replace ( ' ansible_winrm_ ' , ' ' ) for v in host_vars if v . startswith ( ' ansible_winrm_ ' ) ] )
unsupported_args = passed_winrm_args . difference ( supported_winrm_args )
# warn for kwargs unsupported by the installed version of pywinrm
for arg in unsupported_args :
2016-05-04 16:43:27 +00:00
display . warning ( " ansible_winrm_ {0} unsupported by pywinrm (is an up-to-date version of pywinrm installed?) " . format ( arg ) )
2016-04-25 18:20:27 +00:00
# arg names we're going passing directly
internal_kwarg_mask = set ( [ ' self ' , ' endpoint ' , ' transport ' , ' username ' , ' password ' ] )
# pass through matching kwargs, excluding the list we want to treat specially
for arg in passed_winrm_args . difference ( internal_kwarg_mask ) . intersection ( supported_winrm_args ) :
self . _winrm_kwargs [ arg ] = host_vars [ ' ansible_winrm_ %s ' % arg ]
2015-04-27 05:28:25 +00:00
2015-08-23 05:54:46 +00:00
def _winrm_connect ( self ) :
'''
Establish a WinRM connection over HTTP / HTTPS .
'''
2015-11-11 15:10:14 +00:00
display . vvv ( " ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s " %
2015-08-23 05:54:46 +00:00
( self . _winrm_user , self . _winrm_port , self . _winrm_host ) , host = self . _winrm_host )
netloc = ' %s : %d ' % ( self . _winrm_host , self . _winrm_port )
2015-09-17 14:44:26 +00:00
endpoint = urlunsplit ( ( self . _winrm_scheme , netloc , self . _winrm_path , ' ' , ' ' ) )
2015-09-14 19:59:40 +00:00
errors = [ ]
2015-08-23 05:54:46 +00:00
for transport in self . _winrm_transport :
2015-09-14 19:59:40 +00:00
if transport == ' kerberos ' and not HAVE_KERBEROS :
errors . append ( ' kerberos: the python kerberos library is not installed ' )
continue
2015-11-11 15:10:14 +00:00
display . vvvvv ( ' WINRM CONNECT: transport= %s endpoint= %s ' % ( transport , endpoint ) , host = self . _winrm_host )
2014-11-14 22:14:08 +00:00
try :
2015-09-14 19:59:40 +00:00
protocol = Protocol ( endpoint , transport = transport , * * self . _winrm_kwargs )
2016-05-04 16:43:27 +00:00
# open the shell from connect so we know we're able to talk to the server
2016-04-25 18:20:27 +00:00
if not self . shell_id :
self . shell_id = protocol . open_shell ( codepage = 65001 ) # UTF-8
display . vvvvv ( ' WINRM OPEN SHELL: %s ' % self . shell_id , host = self . _winrm_host )
2014-11-14 22:14:08 +00:00
return protocol
2015-09-14 19:59:40 +00:00
except Exception as e :
2016-01-26 03:17:46 +00:00
err_msg = to_unicode ( e ) . strip ( )
2016-01-26 03:46:57 +00:00
if re . search ( to_unicode ( r ' Operation \ s+?timed \ s+?out ' ) , err_msg , re . I ) :
2015-09-14 19:59:40 +00:00
raise AnsibleError ( ' the connection attempt timed out ' )
2016-01-26 03:46:57 +00:00
m = re . search ( to_unicode ( r ' Code \ s+?( \ d {3} ) ' ) , err_msg )
2014-11-14 22:14:08 +00:00
if m :
code = int ( m . groups ( ) [ 0 ] )
if code == 401 :
2016-05-04 16:43:27 +00:00
err_msg = ' the specified credentials were rejected by the server '
2014-11-14 22:14:08 +00:00
elif code == 411 :
return protocol
2016-01-26 03:17:46 +00:00
errors . append ( u ' %s : %s ' % ( transport , err_msg ) )
display . vvvvv ( u ' WINRM CONNECTION ERROR: %s \n %s ' % ( err_msg , to_unicode ( traceback . format_exc ( ) ) ) , host = self . _winrm_host )
2015-09-14 19:59:40 +00:00
if errors :
2016-04-25 18:20:27 +00:00
raise AnsibleConnectionFailure ( ' , ' . join ( map ( to_str , errors ) ) )
2015-09-14 19:59:40 +00:00
else :
raise AnsibleError ( ' No transport found for WinRM connection ' )
2014-11-14 22:14:08 +00:00
2015-10-23 21:37:50 +00:00
def _winrm_send_input ( self , protocol , shell_id , command_id , stdin , eof = False ) :
rq = { ' env:Envelope ' : protocol . _get_soap_header (
resource_uri = ' http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd ' ,
action = ' http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send ' ,
shell_id = shell_id ) }
stream = rq [ ' env:Envelope ' ] . setdefault ( ' env:Body ' , { } ) . setdefault ( ' rsp:Send ' , { } ) \
. setdefault ( ' rsp:Stream ' , { } )
stream [ ' @Name ' ] = ' stdin '
stream [ ' @CommandId ' ] = command_id
stream [ ' #text ' ] = base64 . b64encode ( to_bytes ( stdin ) )
if eof :
stream [ ' @End ' ] = ' true '
rs = protocol . send_message ( xmltodict . unparse ( rq ) )
def _winrm_exec ( self , command , args = ( ) , from_exec = False , stdin_iterator = None ) :
2014-11-14 22:14:08 +00:00
if not self . protocol :
self . protocol = self . _winrm_connect ( )
2015-11-23 16:53:05 +00:00
self . _connected = True
if from_exec :
display . vvvvv ( " WINRM EXEC %r %r " % ( command , args ) , host = self . _winrm_host )
else :
display . vvvvvv ( " WINRM EXEC %r %r " % ( command , args ) , host = self . _winrm_host )
2014-11-14 22:14:08 +00:00
command_id = None
try :
2015-11-19 07:09:16 +00:00
stdin_push_failed = False
2015-10-23 21:37:50 +00:00
command_id = self . protocol . run_command ( self . shell_id , to_bytes ( command ) , map ( to_bytes , args ) , console_mode_stdin = ( stdin_iterator == None ) )
# TODO: try/except around this, so we can get/return the command result on a broken pipe or other failure (probably more useful than the 500 that comes from this)
try :
if stdin_iterator :
for ( data , is_last ) in stdin_iterator :
self . _winrm_send_input ( self . protocol , self . shell_id , command_id , data , eof = is_last )
2016-04-25 18:20:27 +00:00
except Exception as ex :
from traceback import format_exc
display . warning ( " FATAL ERROR DURING FILE TRANSFER: %s " % format_exc ( ex ) )
2015-11-19 07:09:16 +00:00
stdin_push_failed = True
2015-10-23 21:37:50 +00:00
2016-04-25 18:20:27 +00:00
if stdin_push_failed :
raise AnsibleError ( ' winrm send_input failed ' )
# NB: this can hang if the receiver is still running (eg, network failed a Send request but the server's still happy).
2015-11-19 07:09:16 +00:00
# FUTURE: Consider adding pywinrm status check/abort operations to see if the target is still running after a failure.
2014-11-14 22:14:08 +00:00
response = Response ( self . protocol . get_command_output ( self . shell_id , command_id ) )
2016-04-25 18:20:27 +00:00
# TODO: check result from response and set stdin_push_failed if we have nonzero
2014-11-14 22:14:08 +00:00
if from_exec :
2015-11-11 15:10:14 +00:00
display . vvvvv ( ' WINRM RESULT %r ' % to_unicode ( response ) , host = self . _winrm_host )
2014-11-14 22:14:08 +00:00
else :
2015-11-11 15:10:14 +00:00
display . vvvvvv ( ' WINRM RESULT %r ' % to_unicode ( response ) , host = self . _winrm_host )
display . vvvvvv ( ' WINRM STDOUT %s ' % to_unicode ( response . std_out ) , host = self . _winrm_host )
display . vvvvvv ( ' WINRM STDERR %s ' % to_unicode ( response . std_err ) , host = self . _winrm_host )
2015-11-19 07:09:16 +00:00
2016-04-25 18:20:27 +00:00
2015-11-19 07:09:16 +00:00
if stdin_push_failed :
raise AnsibleError ( ' winrm send_input failed; \n stdout: %s \n stderr %s ' % ( response . std_out , response . std_err ) )
2014-11-14 22:14:08 +00:00
return response
finally :
if command_id :
self . protocol . cleanup_command ( self . shell_id , command_id )
2015-04-27 05:28:25 +00:00
def _connect ( self ) :
2014-11-14 22:14:08 +00:00
if not self . protocol :
self . protocol = self . _winrm_connect ( )
2015-11-23 16:53:05 +00:00
self . _connected = True
2014-11-14 22:14:08 +00:00
return self
2016-02-06 08:00:07 +00:00
def _reset ( self ) : # used by win_reboot (and any other action that might need to bounce the state)
self . protocol = None
self . shell_id = None
self . _connect ( )
2015-09-24 20:29:36 +00:00
def exec_command ( self , cmd , in_data = None , sudoable = True ) :
super ( Connection , self ) . exec_command ( cmd , in_data = in_data , sudoable = sudoable )
2015-07-24 16:39:54 +00:00
cmd_parts = shlex . split ( to_bytes ( cmd ) , posix = False )
cmd_parts = map ( to_unicode , cmd_parts )
script = None
cmd_ext = cmd_parts and self . _shell . _unquote ( cmd_parts [ 0 ] ) . lower ( ) [ - 4 : ] or ' '
# Support running .ps1 files (via script/raw).
if cmd_ext == ' .ps1 ' :
2015-08-23 00:18:44 +00:00
script = ' & %s ' % cmd
2015-07-24 16:39:54 +00:00
# Support running .bat/.cmd files; change back to the default system encoding instead of UTF-8.
elif cmd_ext in ( ' .bat ' , ' .cmd ' ) :
2015-08-23 00:18:44 +00:00
script = ' [System.Console]::OutputEncoding = [System.Text.Encoding]::Default; & %s ' % cmd
2015-07-24 16:39:54 +00:00
# Encode the command if not already encoded; supports running simple PowerShell commands via raw.
elif ' -EncodedCommand ' not in cmd_parts :
2015-08-23 00:18:44 +00:00
script = cmd
2015-07-24 16:39:54 +00:00
if script :
2015-08-22 22:19:43 +00:00
cmd_parts = self . _shell . _encode_script ( script , as_list = True , strict_mode = False )
2014-11-14 22:14:08 +00:00
if ' -EncodedCommand ' in cmd_parts :
encoded_cmd = cmd_parts [ cmd_parts . index ( ' -EncodedCommand ' ) + 1 ]
2015-08-23 00:18:44 +00:00
decoded_cmd = to_unicode ( base64 . b64decode ( encoded_cmd ) . decode ( ' utf-16-le ' ) )
2015-11-11 15:10:14 +00:00
display . vvv ( " EXEC %s " % decoded_cmd , host = self . _winrm_host )
2014-11-14 22:14:08 +00:00
else :
2015-11-11 15:10:14 +00:00
display . vvv ( " EXEC %s " % cmd , host = self . _winrm_host )
2014-11-14 22:14:08 +00:00
try :
result = self . _winrm_exec ( cmd_parts [ 0 ] , cmd_parts [ 1 : ] , from_exec = True )
2015-11-11 15:10:14 +00:00
except Exception :
2014-11-14 22:14:08 +00:00
traceback . print_exc ( )
2016-04-25 18:20:27 +00:00
raise AnsibleConnectionFailure ( " failed to exec cmd %s " % cmd )
2015-10-21 14:59:46 +00:00
result . std_out = to_bytes ( result . std_out )
result . std_err = to_bytes ( result . std_err )
2016-04-25 18:20:27 +00:00
# parse just stderr from CLIXML output
if self . is_clixml ( result . std_err ) :
try :
result . std_err = self . parse_clixml_stream ( result . std_err )
except :
2016-05-04 16:43:27 +00:00
# unsure if we're guaranteed a valid xml doc- use raw output in case of error
2016-04-25 18:20:27 +00:00
pass
2015-09-24 15:56:20 +00:00
return ( result . status_code , result . std_out , result . std_err )
2014-11-14 22:14:08 +00:00
2016-04-25 18:20:27 +00:00
def is_clixml ( self , value ) :
return value . startswith ( " #< CLIXML " )
# hacky way to get just stdout- not always sure of doc framing here, so use with care
def parse_clixml_stream ( self , clixml_doc , stream_name = ' Error ' ) :
clear_xml = clixml_doc . replace ( ' #< CLIXML \r \n ' , ' ' )
doc = xmltodict . parse ( clear_xml )
2016-05-04 16:43:27 +00:00
lines = [ l . get ( ' #text ' , ' ' ) . replace ( ' _x000D__x000A_ ' , ' ' ) for l in doc . get ( ' Objs ' , { } ) . get ( ' S ' , { } ) if l . get ( ' @S ' ) == stream_name ]
2016-04-25 18:20:27 +00:00
return ' \r \n ' . join ( lines )
2015-10-23 21:37:50 +00:00
# FUTURE: determine buffer size at runtime via remote winrm config?
def _put_file_stdin_iterator ( self , in_path , out_path , buffer_size = 250000 ) :
2016-03-24 17:00:24 +00:00
in_size = os . path . getsize ( to_bytes ( in_path , errors = ' strict ' ) )
2015-10-23 21:37:50 +00:00
offset = 0
2016-03-24 17:00:24 +00:00
with open ( to_bytes ( in_path , errors = ' strict ' ) , ' rb ' ) as in_file :
2015-10-23 21:37:50 +00:00
for out_data in iter ( ( lambda : in_file . read ( buffer_size ) ) , ' ' ) :
offset + = len ( out_data )
self . _display . vvvvv ( ' WINRM PUT " %s " to " %s " (offset= %d size= %d ) ' % ( in_path , out_path , offset , len ( out_data ) ) , host = self . _winrm_host )
# yes, we're double-encoding over the wire in this case- we want to ensure that the data shipped to the end PS pipeline is still b64-encoded
b64_data = base64 . b64encode ( out_data ) + ' \r \n '
# cough up the data, as well as an indicator if this is the last chunk so winrm_send knows to set the End signal
yield b64_data , ( in_file . tell ( ) == in_size )
if offset == 0 : # empty file, return an empty buffer + eof to close it
yield " " , True
2014-11-14 22:14:08 +00:00
def put_file ( self , in_path , out_path ) :
2015-06-04 18:27:18 +00:00
super ( Connection , self ) . put_file ( in_path , out_path )
2015-07-24 16:39:54 +00:00
out_path = self . _shell . _unquote ( out_path )
2015-11-11 15:10:14 +00:00
display . vvv ( ' PUT " %s " TO " %s " ' % ( in_path , out_path ) , host = self . _winrm_host )
2016-03-24 17:00:24 +00:00
if not os . path . exists ( to_bytes ( in_path , errors = ' strict ' ) ) :
2015-07-24 16:39:54 +00:00
raise AnsibleFileNotFound ( ' file or module does not exist: " %s " ' % in_path )
2015-10-23 21:37:50 +00:00
2016-01-08 00:23:55 +00:00
script_template = u '''
2015-10-23 21:37:50 +00:00
begin { {
2016-03-11 19:00:28 +00:00
$ path = ' {0} '
2015-10-23 21:37:50 +00:00
$ DebugPreference = " Continue "
$ ErrorActionPreference = " Stop "
Set - StrictMode - Version 2
$ fd = [ System . IO . File ] : : Create ( $ path )
$ sha1 = [ System . Security . Cryptography . SHA1CryptoServiceProvider ] : : Create ( )
$ bytes = @ ( ) #initialize for empty file case
} }
process { {
$ bytes = [ System . Convert ] : : FromBase64String ( $ input )
$ sha1 . TransformBlock ( $ bytes , 0 , $ bytes . Length , $ bytes , 0 ) | Out - Null
$ fd . Write ( $ bytes , 0 , $ bytes . Length )
} }
end { {
$ sha1 . TransformFinalBlock ( $ bytes , 0 , 0 ) | Out - Null
$ hash = [ System . BitConverter ] : : ToString ( $ sha1 . Hash ) . Replace ( " - " , " " ) . ToLowerInvariant ( )
$ fd . Close ( )
Write - Output " {{ " " sha1 " " : " " $hash " " }} "
} }
'''
script = script_template . format ( self . _shell . _escape ( out_path ) )
cmd_parts = self . _shell . _encode_script ( script , as_list = True , strict_mode = False )
result = self . _winrm_exec ( cmd_parts [ 0 ] , cmd_parts [ 1 : ] , stdin_iterator = self . _put_file_stdin_iterator ( in_path , out_path ) )
# TODO: improve error handling
if result . status_code != 0 :
2015-11-19 07:09:16 +00:00
raise AnsibleError ( to_str ( result . std_err ) )
2015-10-23 21:37:50 +00:00
put_output = json . loads ( result . std_out )
remote_sha1 = put_output . get ( " sha1 " )
if not remote_sha1 :
2015-11-19 07:09:16 +00:00
raise AnsibleError ( " Remote sha1 was not returned " )
2015-10-23 21:37:50 +00:00
local_sha1 = secure_hash ( in_path )
if not remote_sha1 == local_sha1 :
2016-01-06 23:18:22 +00:00
raise AnsibleError ( " Remote sha1 hash {0} does not match local hash {1} " . format ( to_str ( remote_sha1 ) , to_str ( local_sha1 ) ) )
2015-10-23 21:37:50 +00:00
2014-11-14 22:14:08 +00:00
def fetch_file ( self , in_path , out_path ) :
2015-06-04 18:27:18 +00:00
super ( Connection , self ) . fetch_file ( in_path , out_path )
2015-07-24 16:39:54 +00:00
in_path = self . _shell . _unquote ( in_path )
2014-11-14 22:14:08 +00:00
out_path = out_path . replace ( ' \\ ' , ' / ' )
2015-11-11 15:10:14 +00:00
display . vvv ( ' FETCH " %s " TO " %s " ' % ( in_path , out_path ) , host = self . _winrm_host )
2015-04-23 23:54:48 +00:00
buffer_size = 2 * * 19 # 0.5MB chunks
2015-06-02 15:41:30 +00:00
makedirs_safe ( os . path . dirname ( out_path ) )
2014-11-14 22:14:08 +00:00
out_file = None
try :
offset = 0
while True :
try :
script = '''
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 ;
}
2015-04-27 05:28:25 +00:00
''' % d ict(buffer_size=buffer_size, path=self._shell._escape(in_path), offset=offset)
2015-11-11 15:10:14 +00:00
display . vvvvv ( ' WINRM FETCH " %s " to " %s " (offset= %d ) ' % ( in_path , out_path , offset ) , host = self . _winrm_host )
2015-04-27 05:28:25 +00:00
cmd_parts = self . _shell . _encode_script ( script , as_list = True )
2014-11-14 22:14:08 +00:00
result = self . _winrm_exec ( cmd_parts [ 0 ] , cmd_parts [ 1 : ] )
if result . status_code != 0 :
2015-10-21 14:59:46 +00:00
raise IOError ( to_str ( result . std_err ) )
2014-11-14 22:14:08 +00:00
if result . std_out . strip ( ) == ' [DIR] ' :
data = None
else :
data = base64 . b64decode ( result . std_out . strip ( ) )
if data is None :
2015-06-02 15:41:30 +00:00
makedirs_safe ( out_path )
2014-11-14 22:14:08 +00:00
break
else :
if not out_file :
# If out_path is a directory and we're expecting a file, bail out now.
2016-03-24 17:00:24 +00:00
if os . path . isdir ( to_bytes ( out_path , errors = ' strict ' ) ) :
2014-11-14 22:14:08 +00:00
break
2016-03-24 17:00:24 +00:00
out_file = open ( to_bytes ( out_path , errors = ' strict ' ) , ' wb ' )
2014-11-14 22:14:08 +00:00
out_file . write ( data )
if len ( data ) < buffer_size :
break
offset + = len ( data )
except Exception :
traceback . print_exc ( )
2015-07-24 16:39:54 +00:00
raise AnsibleError ( ' failed to transfer file to " %s " ' % out_path )
2014-11-14 22:14:08 +00:00
finally :
if out_file :
out_file . close ( )
def close ( self ) :
if self . protocol and self . shell_id :
2015-11-23 16:53:05 +00:00
display . vvvvv ( ' WINRM CLOSE SHELL: %s ' % self . shell_id , host = self . _winrm_host )
2014-11-14 22:14:08 +00:00
self . protocol . close_shell ( self . shell_id )
2015-11-23 16:53:05 +00:00
self . shell_id = None
self . protocol = None
self . _connected = False