192 lines
7.4 KiB
Python
192 lines
7.4 KiB
Python
# (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/>.
|
|
from __future__ import (absolute_import, division, print_function)
|
|
__metaclass__ = type
|
|
|
|
import base64
|
|
import os
|
|
import re
|
|
import random
|
|
import shlex
|
|
import time
|
|
|
|
from ansible.utils.unicode import to_bytes, to_unicode
|
|
|
|
_common_args = ['PowerShell', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Unrestricted']
|
|
|
|
# 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:]
|
|
|
|
class ShellModule(object):
|
|
|
|
def env_prefix(self, **kwargs):
|
|
return ''
|
|
|
|
def join_path(self, *args):
|
|
parts = []
|
|
for arg in args:
|
|
arg = self._unquote(arg).replace('/', '\\')
|
|
parts.extend([a for a in arg.split('\\') if a])
|
|
path = '\\'.join(parts)
|
|
if path.startswith('~'):
|
|
return path
|
|
return '"%s"' % path
|
|
|
|
def path_has_trailing_slash(self, path):
|
|
# Allow Windows paths to be specified using either slash.
|
|
path = self._unquote(path)
|
|
return path.endswith('/') or path.endswith('\\')
|
|
|
|
def chmod(self, mode, path):
|
|
return ''
|
|
|
|
def remove(self, path, recurse=False):
|
|
path = self._escape(self._unquote(path))
|
|
if recurse:
|
|
return self._encode_script('''Remove-Item "%s" -Force -Recurse;''' % path)
|
|
else:
|
|
return self._encode_script('''Remove-Item "%s" -Force;''' % path)
|
|
|
|
def mkdtemp(self, basefile, system=False, mode=None):
|
|
basefile = self._escape(self._unquote(basefile))
|
|
# FIXME: Support system temp path!
|
|
return self._encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName | Write-Host -Separator '';''' % basefile)
|
|
|
|
def expand_user(self, user_home_path):
|
|
# PowerShell only supports "~" (not "~username"). Resolve-Path ~ does
|
|
# not seem to work remotely, though by default we are always starting
|
|
# in the user's home directory.
|
|
user_home_path = self._unquote(user_home_path)
|
|
if user_home_path == '~':
|
|
script = 'Write-Host (Get-Location).Path'
|
|
elif user_home_path.startswith('~\\'):
|
|
script = 'Write-Host ((Get-Location).Path + "%s")' % self._escape(user_home_path[1:])
|
|
else:
|
|
script = 'Write-Host "%s"' % self._escape(user_home_path)
|
|
return self._encode_script(script)
|
|
|
|
def checksum(self, path, *args, **kwargs):
|
|
path = self._escape(self._unquote(path))
|
|
script = '''
|
|
If (Test-Path -PathType Leaf "%(path)s")
|
|
{
|
|
$sp = new-object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider;
|
|
$fp = [System.IO.File]::Open("%(path)s", [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read);
|
|
[System.BitConverter]::ToString($sp.ComputeHash($fp)).Replace("-", "").ToLower();
|
|
$fp.Dispose();
|
|
}
|
|
ElseIf (Test-Path -PathType Container "%(path)s")
|
|
{
|
|
Write-Host "3";
|
|
}
|
|
Else
|
|
{
|
|
Write-Host "1";
|
|
}
|
|
''' % dict(path=path)
|
|
return self._encode_script(script)
|
|
|
|
def build_module_command(self, env_string, shebang, cmd, rm_tmp=None):
|
|
cmd_parts = shlex.split(to_bytes(cmd), posix=False)
|
|
cmd_parts = map(to_unicode, cmd_parts)
|
|
if shebang and shebang.lower() == '#!powershell':
|
|
if not self._unquote(cmd_parts[0]).lower().endswith('.ps1'):
|
|
cmd_parts[0] = '"%s.ps1"' % self._unquote(cmd_parts[0])
|
|
cmd_parts.insert(0, '&')
|
|
elif shebang and shebang.startswith('#!'):
|
|
cmd_parts.insert(0, shebang[2:])
|
|
script = '''
|
|
Try
|
|
{
|
|
%s
|
|
}
|
|
Catch
|
|
{
|
|
$_obj = @{ failed = $true }
|
|
If ($_.Exception.GetType)
|
|
{
|
|
$_obj.Add('msg', $_.Exception.Message)
|
|
}
|
|
Else
|
|
{
|
|
$_obj.Add('msg', $_.ToString())
|
|
}
|
|
If ($_.InvocationInfo.PositionMessage)
|
|
{
|
|
$_obj.Add('exception', $_.InvocationInfo.PositionMessage)
|
|
}
|
|
ElseIf ($_.ScriptStackTrace)
|
|
{
|
|
$_obj.Add('exception', $_.ScriptStackTrace)
|
|
}
|
|
Try
|
|
{
|
|
$_obj.Add('error_record', ($_ | ConvertTo-Json | ConvertFrom-Json))
|
|
}
|
|
Catch
|
|
{
|
|
}
|
|
Echo $_obj | ConvertTo-Json -Compress -Depth 99
|
|
Exit 1
|
|
}
|
|
''' % (' '.join(cmd_parts))
|
|
if rm_tmp:
|
|
rm_tmp = self._escape(self._unquote(rm_tmp))
|
|
rm_cmd = 'Remove-Item "%s" -Force -Recurse -ErrorAction SilentlyContinue' % rm_tmp
|
|
script = '%s\nFinally { %s }' % (script, rm_cmd)
|
|
return self._encode_script(script)
|
|
|
|
def _unquote(self, value):
|
|
'''Remove any matching quotes that wrap the given value.'''
|
|
value = to_unicode(value or '')
|
|
m = re.match(r'^\s*?\'(.*?)\'\s*?$', value)
|
|
if m:
|
|
return m.group(1)
|
|
m = re.match(r'^\s*?"(.*?)"\s*?$', value)
|
|
if m:
|
|
return m.group(1)
|
|
return value
|
|
|
|
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 _encode_script(self, script, as_list=False, strict_mode=True):
|
|
'''Convert a PowerShell script to a single base64-encoded command.'''
|
|
script = to_unicode(script)
|
|
if strict_mode:
|
|
script = u'Set-StrictMode -Version Latest\r\n%s' % script
|
|
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:
|
|
return cmd_parts
|
|
return ' '.join(cmd_parts)
|