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-13 20:28:01 +00:00
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
|
|
__metaclass__ = type
|
2014-11-14 22:14:08 +00:00
|
|
|
|
|
|
|
import base64
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import shlex
|
|
|
|
|
2016-09-06 20:00:21 +00:00
|
|
|
from ansible.errors import AnsibleError
|
2016-09-07 05:54:17 +00:00
|
|
|
from ansible.module_utils._text import to_bytes, to_text
|
|
|
|
|
2015-07-24 16:39:54 +00:00
|
|
|
|
|
|
|
_common_args = ['PowerShell', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Unrestricted']
|
2014-11-14 22:14:08 +00:00
|
|
|
|
|
|
|
# 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:]
|
|
|
|
|
2017-02-17 08:09:56 +00:00
|
|
|
exec_wrapper = br'''
|
|
|
|
#Requires -Version 3.0
|
|
|
|
begin {
|
|
|
|
$DebugPreference = "Continue"
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
Set-StrictMode -Version 2
|
|
|
|
|
|
|
|
function ConvertTo-HashtableFromPsCustomObject ($myPsObject){
|
|
|
|
$output = @{};
|
|
|
|
$myPsObject | Get-Member -MemberType *Property | % {
|
|
|
|
$val = $myPsObject.($_.name);
|
|
|
|
If ($val -is [psobject]) {
|
|
|
|
$val = ConvertTo-HashtableFromPsCustomObject $val
|
|
|
|
}
|
|
|
|
$output.($_.name) = $val
|
|
|
|
}
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
# stream JSON including become_pw, ps_module_payload, bin_module_payload, become_payload, write_payload_path, preserve directives
|
|
|
|
# exec runspace, capture output, cleanup, return module output
|
|
|
|
|
2017-06-27 05:58:09 +00:00
|
|
|
# NB: do not adjust the following line- it is replaced when doing non-streamed module output
|
|
|
|
$json_raw = ''
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
|
|
|
process {
|
|
|
|
$input_as_string = [string]$input
|
|
|
|
|
|
|
|
$json_raw += $input_as_string
|
|
|
|
}
|
|
|
|
end {
|
|
|
|
If (-not $json_raw) {
|
|
|
|
Write-Error "no input given" -Category InvalidArgument
|
|
|
|
}
|
|
|
|
$payload = ConvertTo-HashtableFromPsCustomObject (ConvertFrom-Json $json_raw)
|
|
|
|
|
|
|
|
# TODO: handle binary modules
|
|
|
|
# TODO: handle persistence
|
|
|
|
|
|
|
|
$actions = $payload.actions
|
|
|
|
|
|
|
|
# pop 0th action as entrypoint
|
|
|
|
$entrypoint = $payload.($actions[0])
|
|
|
|
$payload.actions = $payload.actions[1..99]
|
|
|
|
|
|
|
|
$entrypoint = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($entrypoint))
|
|
|
|
|
|
|
|
# load the current action entrypoint as a module custom object with a Run method
|
|
|
|
$entrypoint = New-Module -ScriptBlock ([scriptblock]::Create($entrypoint)) -AsCustomObject
|
|
|
|
|
|
|
|
Set-Variable -Scope global -Name complex_args -Value $payload["module_args"] | Out-Null
|
|
|
|
|
|
|
|
# dynamically create/load modules
|
|
|
|
ForEach ($mod in $payload.powershell_modules.GetEnumerator()) {
|
|
|
|
$decoded_module = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($mod.Value))
|
2017-02-27 22:29:43 +00:00
|
|
|
New-Module -ScriptBlock ([scriptblock]::Create($decoded_module)) -Name $mod.Key | Import-Module -WarningAction SilentlyContinue | Out-Null
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$output = $entrypoint.Run($payload)
|
|
|
|
|
|
|
|
Write-Output $output
|
|
|
|
}
|
|
|
|
|
2017-06-02 11:14:11 +00:00
|
|
|
''' # end exec_wrapper
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
leaf_exec = br'''
|
|
|
|
Function Run($payload) {
|
|
|
|
$entrypoint = $payload.module_entry
|
|
|
|
|
|
|
|
$entrypoint = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($entrypoint))
|
|
|
|
|
|
|
|
$ps = [powershell]::Create()
|
|
|
|
|
|
|
|
$ps.AddStatement().AddCommand("Set-Variable").AddParameters(@{Scope="global";Name="complex_args";Value=$payload.module_args}) | Out-Null
|
|
|
|
$ps.AddCommand("Out-Null") | Out-Null
|
|
|
|
|
|
|
|
# redefine Write-Host to dump to output instead of failing- lots of scripts use it
|
|
|
|
$ps.AddStatement().AddScript("Function Write-Host(`$msg){ Write-Output `$msg }") | Out-Null
|
|
|
|
|
2017-03-24 00:48:15 +00:00
|
|
|
ForEach ($env_kv in $payload.environment.GetEnumerator()) {
|
|
|
|
$escaped_env_set = "`$env:{0} = '{1}'" -f $env_kv.Key,$env_kv.Value.Replace("'","''")
|
|
|
|
$ps.AddStatement().AddScript($escaped_env_set) | Out-Null
|
|
|
|
}
|
|
|
|
|
2017-02-17 08:09:56 +00:00
|
|
|
# dynamically create/load modules
|
|
|
|
ForEach ($mod in $payload.powershell_modules.GetEnumerator()) {
|
|
|
|
$decoded_module = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($mod.Value))
|
|
|
|
$ps.AddStatement().AddCommand("New-Module").AddParameters(@{ScriptBlock=([scriptblock]::Create($decoded_module));Name=$mod.Key}) | Out-Null
|
2017-02-27 22:29:43 +00:00
|
|
|
$ps.AddCommand("Import-Module").AddParameters(@{WarningAction="SilentlyContinue"}) | Out-Null
|
2017-02-17 08:09:56 +00:00
|
|
|
$ps.AddCommand("Out-Null") | Out-Null
|
|
|
|
}
|
|
|
|
|
2017-03-24 07:02:39 +00:00
|
|
|
# force input encoding to preamble-free UTF8 so PS sub-processes (eg, Start-Job) don't blow up
|
|
|
|
$ps.AddStatement().AddScript("[Console]::InputEncoding = New-Object Text.UTF8Encoding `$false") | Out-Null
|
|
|
|
|
2017-02-17 08:09:56 +00:00
|
|
|
$ps.AddStatement().AddScript($entrypoint) | Out-Null
|
|
|
|
|
|
|
|
$output = $ps.Invoke()
|
|
|
|
|
|
|
|
$output
|
|
|
|
|
|
|
|
# PS3 doesn't properly set HadErrors in many cases, inspect the error stream as a fallback
|
|
|
|
If ($ps.HadErrors -or ($PSVersionTable.PSVersion.Major -lt 4 -and $ps.Streams.Error.Count -gt 0)) {
|
|
|
|
[System.Console]::Error.WriteLine($($ps.Streams.Error | Out-String))
|
|
|
|
$exit_code = $ps.Runspace.SessionStateProxy.GetVariable("LASTEXITCODE")
|
|
|
|
If(-not $exit_code) {
|
|
|
|
$exit_code = 1
|
|
|
|
}
|
2017-03-24 00:48:15 +00:00
|
|
|
# need to use this instead of Exit keyword to prevent runspace from crashing with dynamic modules
|
2017-02-17 08:09:56 +00:00
|
|
|
$host.SetShouldExit($exit_code)
|
|
|
|
}
|
|
|
|
}
|
2017-06-02 11:14:11 +00:00
|
|
|
''' # end leaf_exec
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
become_wrapper = br'''
|
|
|
|
Set-StrictMode -Version 2
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
|
|
|
|
$helper_def = @"
|
|
|
|
using System;
|
2017-10-10 00:58:29 +00:00
|
|
|
using System.Collections.Generic;
|
2017-02-17 08:09:56 +00:00
|
|
|
using System.IO;
|
2017-10-10 00:58:29 +00:00
|
|
|
using System.Runtime.InteropServices;
|
2017-02-17 08:09:56 +00:00
|
|
|
using System.Security.AccessControl;
|
|
|
|
using System.Security.Principal;
|
2017-10-10 00:58:29 +00:00
|
|
|
using System.Text;
|
|
|
|
using System.Threading;
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
namespace Ansible
|
2017-02-17 08:09:56 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
public class SECURITY_ATTRIBUTES
|
2017-02-17 08:09:56 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
public int nLength;
|
|
|
|
public IntPtr lpSecurityDescriptor;
|
|
|
|
public bool bInheritHandle = false;
|
|
|
|
public SECURITY_ATTRIBUTES()
|
2017-02-17 08:09:56 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
nLength = Marshal.SizeOf(this);
|
|
|
|
}
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
public class STARTUPINFO
|
|
|
|
{
|
|
|
|
public Int32 cb;
|
|
|
|
public IntPtr lpReserved;
|
|
|
|
public IntPtr lpDesktop;
|
|
|
|
public IntPtr lpTitle;
|
|
|
|
public Int32 dwX;
|
|
|
|
public Int32 dwY;
|
|
|
|
public Int32 dwXSize;
|
|
|
|
public Int32 dwYSize;
|
|
|
|
public Int32 dwXCountChars;
|
|
|
|
public Int32 dwYCountChars;
|
|
|
|
public Int32 dwFillAttribute;
|
|
|
|
public Int32 dwFlags;
|
|
|
|
public Int16 wShowWindow;
|
|
|
|
public Int16 cbReserved2;
|
|
|
|
public IntPtr lpReserved2;
|
|
|
|
public IntPtr hStdInput;
|
|
|
|
public IntPtr hStdOutput;
|
|
|
|
public IntPtr hStdError;
|
|
|
|
public STARTUPINFO()
|
|
|
|
{
|
|
|
|
cb = Marshal.SizeOf(this);
|
|
|
|
}
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
public class STARTUPINFOEX
|
|
|
|
{
|
|
|
|
public STARTUPINFO startupInfo;
|
|
|
|
public IntPtr lpAttributeList;
|
|
|
|
public STARTUPINFOEX()
|
|
|
|
{
|
|
|
|
startupInfo = new STARTUPINFO();
|
|
|
|
startupInfo.cb = Marshal.SizeOf(this);
|
|
|
|
}
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
public struct PROCESS_INFORMATION
|
|
|
|
{
|
|
|
|
public IntPtr hProcess;
|
|
|
|
public IntPtr hThread;
|
|
|
|
public int dwProcessId;
|
|
|
|
public int dwThreadId;
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
public struct SID_AND_ATTRIBUTES
|
|
|
|
{
|
|
|
|
public IntPtr Sid;
|
|
|
|
public int Attributes;
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
public struct TOKEN_USER
|
|
|
|
{
|
|
|
|
public SID_AND_ATTRIBUTES User;
|
|
|
|
}
|
|
|
|
|
|
|
|
[Flags]
|
|
|
|
public enum StartupInfoFlags : uint
|
|
|
|
{
|
|
|
|
USESTDHANDLES = 0x00000100
|
|
|
|
}
|
|
|
|
|
|
|
|
[Flags]
|
|
|
|
public enum CreationFlags : uint
|
|
|
|
{
|
|
|
|
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
|
|
|
|
CREATE_NEW_CONSOLE = 0x00000010,
|
|
|
|
CREATE_NEW_PROCESS_GROUP = 0x00000200,
|
|
|
|
CREATE_SEPARATE_WOW_VDM = 0x00000800,
|
|
|
|
CREATE_SUSPENDED = 0x00000004,
|
|
|
|
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
|
|
|
|
EXTENDED_STARTUPINFO_PRESENT = 0x00080000
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum HandleFlags : uint
|
|
|
|
{
|
|
|
|
None = 0,
|
|
|
|
INHERIT = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
[Flags]
|
|
|
|
public enum LogonFlags
|
|
|
|
{
|
|
|
|
LOGON_WITH_PROFILE = 0x00000001,
|
|
|
|
LOGON_NETCREDENTIALS_ONLY = 0x00000002
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum LogonType
|
|
|
|
{
|
|
|
|
LOGON32_LOGON_INTERACTIVE = 2,
|
|
|
|
LOGON32_LOGON_NETWORK = 3,
|
|
|
|
LOGON32_LOGON_BATCH = 4,
|
|
|
|
LOGON32_LOGON_SERVICE = 5,
|
|
|
|
LOGON32_LOGON_UNLOCK = 7,
|
|
|
|
LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
|
|
|
|
LOGON32_LOGON_NEW_CREDENTIALS = 9
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum LogonProvider
|
|
|
|
{
|
|
|
|
LOGON32_PROVIDER_DEFAULT = 0,
|
|
|
|
LOGON32_PROVIDER_WINNT35 = 1,
|
|
|
|
LOGON32_PROVIDER_WINNT40 = 2,
|
|
|
|
LOGON32_PROVIDER_WINNT50 = 3
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum TokenInformationClass
|
|
|
|
{
|
|
|
|
TokenUser = 1,
|
|
|
|
TokenType = 8,
|
|
|
|
TokenImpersonationLevel = 9,
|
|
|
|
TokenElevationType = 18,
|
|
|
|
TokenLinkedToken = 19,
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum TokenElevationType
|
|
|
|
{
|
|
|
|
TokenElevationTypeDefault = 1,
|
|
|
|
TokenElevationTypeFull,
|
|
|
|
TokenElevationTypeLimited
|
|
|
|
}
|
|
|
|
|
|
|
|
[Flags]
|
|
|
|
public enum ProcessAccessFlags : uint
|
|
|
|
{
|
|
|
|
PROCESS_ALL_ACCESS = 0x001F0FFF,
|
|
|
|
PROCESS_TERMINATE = 0x00000001,
|
|
|
|
PROCESS_CREATE_THREAD = 0x00000002,
|
|
|
|
PROCESS_VM_OPERATION = 0x00000008,
|
|
|
|
PROCESS_VM_READ = 0x00000010,
|
|
|
|
PROCESS_VM_WRITE = 0x00000020,
|
|
|
|
PROCESS_DUP_HANDLE = 0x00000040,
|
|
|
|
PROCESS_CREATE_PROCESS = 0x000000080,
|
|
|
|
PROCESS_SET_QUOTA = 0x00000100,
|
|
|
|
PROCESS_SET_INFORMATION = 0x00000200,
|
|
|
|
PROCESS_QUERY_INFORMATION = 0x00000400,
|
|
|
|
PROCESS_SUSPEND_RESUME = 0x00000800,
|
|
|
|
PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000,
|
|
|
|
SYNCHRONIZE = 0x00100000
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum SECURITY_IMPERSONATION_LEVEL
|
|
|
|
{
|
|
|
|
SecurityAnoynmous,
|
|
|
|
SecurityIdentification,
|
|
|
|
SecurityImpersonation,
|
|
|
|
SecurityDelegation
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum TOKEN_TYPE
|
|
|
|
{
|
|
|
|
TokenPrimary = 1,
|
|
|
|
TokenImpersonation
|
|
|
|
}
|
|
|
|
|
|
|
|
class NativeWaitHandle : WaitHandle
|
|
|
|
{
|
|
|
|
public NativeWaitHandle(IntPtr handle)
|
|
|
|
{
|
|
|
|
this.Handle = handle;
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
2017-10-10 00:58:29 +00:00
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
public class Win32Exception : System.ComponentModel.Win32Exception
|
|
|
|
{
|
|
|
|
private string _msg;
|
|
|
|
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
|
|
|
|
public Win32Exception(int errorCode, string message) : base(errorCode)
|
2017-08-16 01:55:17 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
_msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
|
|
|
|
}
|
|
|
|
public override string Message { get { return _msg; } }
|
|
|
|
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
|
|
|
|
}
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
public class CommandResult
|
|
|
|
{
|
|
|
|
public string StandardOut { get; internal set; }
|
|
|
|
public string StandardError { get; internal set; }
|
|
|
|
public uint ExitCode { get; internal set; }
|
|
|
|
}
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
public class BecomeUtil
|
|
|
|
{
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
|
|
private static extern bool LogonUser(
|
|
|
|
string lpszUsername,
|
|
|
|
string lpszDomain,
|
|
|
|
string lpszPassword,
|
|
|
|
LogonType dwLogonType,
|
|
|
|
LogonProvider dwLogonProvider,
|
|
|
|
out IntPtr phToken);
|
|
|
|
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
|
|
private static extern bool CreateProcessWithTokenW(
|
|
|
|
IntPtr hToken,
|
|
|
|
LogonFlags dwLogonFlags,
|
|
|
|
[MarshalAs(UnmanagedType.LPTStr)]
|
|
|
|
string lpApplicationName,
|
|
|
|
StringBuilder lpCommandLine,
|
|
|
|
CreationFlags dwCreationFlags,
|
|
|
|
IntPtr lpEnvironment,
|
|
|
|
[MarshalAs(UnmanagedType.LPTStr)]
|
|
|
|
string lpCurrentDirectory,
|
|
|
|
STARTUPINFOEX lpStartupInfo,
|
|
|
|
out PROCESS_INFORMATION lpProcessInformation);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll")]
|
|
|
|
private static extern bool CreatePipe(
|
|
|
|
out IntPtr hReadPipe,
|
|
|
|
out IntPtr hWritePipe,
|
|
|
|
SECURITY_ATTRIBUTES lpPipeAttributes,
|
|
|
|
uint nSize);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
|
|
private static extern bool SetHandleInformation(
|
|
|
|
IntPtr hObject,
|
|
|
|
HandleFlags dwMask,
|
|
|
|
int dwFlags);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
|
|
private static extern bool GetExitCodeProcess(
|
|
|
|
IntPtr hProcess,
|
|
|
|
out uint lpExitCode);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
|
|
private static extern bool CloseHandle(
|
|
|
|
IntPtr hObject);
|
|
|
|
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
|
|
private static extern IntPtr GetProcessWindowStation();
|
|
|
|
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
|
|
private static extern IntPtr GetThreadDesktop(int dwThreadId);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
|
|
private static extern int GetCurrentThreadId();
|
|
|
|
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
|
|
private static extern bool GetTokenInformation(
|
|
|
|
IntPtr TokenHandle,
|
|
|
|
TokenInformationClass TokenInformationClass,
|
|
|
|
IntPtr TokenInformation,
|
|
|
|
uint TokenInformationLength,
|
|
|
|
out uint ReturnLength);
|
|
|
|
|
|
|
|
[DllImport("psapi.dll", SetLastError = true)]
|
|
|
|
private static extern bool EnumProcesses(
|
|
|
|
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U4)]
|
|
|
|
[In][Out] IntPtr[] processIds,
|
|
|
|
uint cb,
|
|
|
|
[MarshalAs(UnmanagedType.U4)]
|
|
|
|
out uint pBytesReturned);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
|
|
private static extern IntPtr OpenProcess(
|
|
|
|
ProcessAccessFlags processAccess,
|
|
|
|
bool bInheritHandle,
|
|
|
|
IntPtr processId);
|
|
|
|
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
|
|
private static extern bool OpenProcessToken(
|
|
|
|
IntPtr ProcessHandle,
|
|
|
|
TokenAccessLevels DesiredAccess,
|
|
|
|
out IntPtr TokenHandle);
|
|
|
|
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
|
|
private static extern bool ConvertSidToStringSidW(
|
|
|
|
IntPtr pSID,
|
|
|
|
[MarshalAs(UnmanagedType.LPTStr)]
|
|
|
|
out string StringSid);
|
|
|
|
|
|
|
|
[DllImport("advapi32", SetLastError = true)]
|
|
|
|
private static extern bool DuplicateTokenEx(
|
|
|
|
IntPtr hExistingToken,
|
|
|
|
TokenAccessLevels dwDesiredAccess,
|
|
|
|
IntPtr lpTokenAttributes,
|
|
|
|
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
|
|
|
|
TOKEN_TYPE TokenType,
|
|
|
|
out IntPtr phNewToken);
|
|
|
|
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
|
|
public static extern bool ImpersonateLoggedOnUser(
|
|
|
|
IntPtr hToken);
|
|
|
|
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
|
|
public static extern bool RevertToSelf();
|
|
|
|
|
|
|
|
public static CommandResult RunAsUser(string username, string password, string lpCommandLine, string lpCurrentDirectory, string stdinInput)
|
|
|
|
{
|
|
|
|
SecurityIdentifier account = GetBecomeSid(username);
|
|
|
|
|
|
|
|
CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT;
|
|
|
|
|
|
|
|
STARTUPINFOEX si = new STARTUPINFOEX();
|
|
|
|
si.startupInfo.dwFlags = (int)StartupInfoFlags.USESTDHANDLES;
|
|
|
|
|
|
|
|
SECURITY_ATTRIBUTES pipesec = new SECURITY_ATTRIBUTES();
|
|
|
|
pipesec.bInheritHandle = true;
|
|
|
|
|
|
|
|
// Create the stdout, stderr and stdin pipes used in the process and add to the startupInfo
|
|
|
|
IntPtr stdout_read, stdout_write, stderr_read, stderr_write, stdin_read, stdin_write = IntPtr.Zero;
|
|
|
|
if (!CreatePipe(out stdout_read, out stdout_write, pipesec, 0))
|
|
|
|
throw new Win32Exception("STDOUT pipe setup failed");
|
|
|
|
if (!SetHandleInformation(stdout_read, HandleFlags.INHERIT, 0))
|
|
|
|
throw new Win32Exception("STDOUT pipe handle setup failed");
|
|
|
|
|
|
|
|
if (!CreatePipe(out stderr_read, out stderr_write, pipesec, 0))
|
|
|
|
throw new Win32Exception("STDERR pipe setup failed");
|
|
|
|
if (!SetHandleInformation(stderr_read, HandleFlags.INHERIT, 0))
|
|
|
|
throw new Win32Exception("STDERR pipe handle setup failed");
|
|
|
|
|
|
|
|
if (!CreatePipe(out stdin_read, out stdin_write, pipesec, 0))
|
|
|
|
throw new Win32Exception("STDIN pipe setup failed");
|
|
|
|
if (!SetHandleInformation(stdin_write, HandleFlags.INHERIT, 0))
|
|
|
|
throw new Win32Exception("STDIN pipe handle setup failed");
|
|
|
|
|
|
|
|
si.startupInfo.hStdOutput = stdout_write;
|
|
|
|
si.startupInfo.hStdError = stderr_write;
|
|
|
|
si.startupInfo.hStdInput = stdin_read;
|
|
|
|
|
|
|
|
// Setup the stdin buffer
|
|
|
|
UTF8Encoding utf8_encoding = new UTF8Encoding(false);
|
|
|
|
FileStream stdin_fs = new FileStream(stdin_write, FileAccess.Write, true, 32768);
|
|
|
|
StreamWriter stdin = new StreamWriter(stdin_fs, utf8_encoding, 32768);
|
|
|
|
|
|
|
|
// Create the environment block if set
|
|
|
|
IntPtr lpEnvironment = IntPtr.Zero;
|
|
|
|
|
|
|
|
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
|
|
|
|
|
|
|
|
// Get the user tokens to try running processes with
|
|
|
|
List<IntPtr> tokens = GetUserTokens(account, username, password);
|
|
|
|
|
|
|
|
bool launch_success = false;
|
|
|
|
foreach (IntPtr token in tokens)
|
2017-08-16 01:55:17 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
if (CreateProcessWithTokenW(
|
|
|
|
token,
|
|
|
|
LogonFlags.LOGON_WITH_PROFILE,
|
|
|
|
null,
|
|
|
|
new StringBuilder(lpCommandLine),
|
|
|
|
startup_flags,
|
|
|
|
lpEnvironment,
|
|
|
|
lpCurrentDirectory,
|
|
|
|
si,
|
|
|
|
out pi))
|
|
|
|
{
|
|
|
|
launch_success = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
if (!launch_success)
|
|
|
|
throw new Win32Exception("Failed to start become process");
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
// Setup the output buffers and get stdout/stderr
|
|
|
|
FileStream stdout_fs = new FileStream(stdout_read, FileAccess.Read, true, 4096);
|
|
|
|
StreamReader stdout = new StreamReader(stdout_fs, utf8_encoding, true, 4096);
|
|
|
|
CloseHandle(stdout_write);
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
FileStream stderr_fs = new FileStream(stderr_read, FileAccess.Read, true, 4096);
|
|
|
|
StreamReader stderr = new StreamReader(stderr_fs, utf8_encoding, true, 4096);
|
|
|
|
CloseHandle(stderr_write);
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
stdin.WriteLine(stdinInput);
|
|
|
|
stdin.Close();
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
string stdout_str, stderr_str = null;
|
|
|
|
GetProcessOutput(stdout, stderr, out stdout_str, out stderr_str);
|
|
|
|
uint rc = GetProcessExitCode(pi.hProcess);
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
CommandResult result = new CommandResult();
|
|
|
|
result.StandardOut = stdout_str;
|
|
|
|
result.StandardError = stderr_str;
|
|
|
|
result.ExitCode = rc;
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
return result;
|
|
|
|
}
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
private static SecurityIdentifier GetBecomeSid(string username)
|
|
|
|
{
|
|
|
|
NTAccount account = new NTAccount(username);
|
|
|
|
try
|
|
|
|
{
|
|
|
|
SecurityIdentifier security_identifier = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
|
|
|
|
return security_identifier;
|
2017-08-16 01:55:17 +00:00
|
|
|
}
|
2017-10-10 00:58:29 +00:00
|
|
|
catch (IdentityNotMappedException ex)
|
2017-08-16 01:55:17 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
throw new Exception(String.Format("Unable to find become user {0}: {1}", username, ex.Message));
|
2017-08-16 01:55:17 +00:00
|
|
|
}
|
2017-10-10 00:58:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private static List<IntPtr> GetUserTokens(SecurityIdentifier account, string username, string password)
|
|
|
|
{
|
|
|
|
List<IntPtr> tokens = new List<IntPtr>();
|
|
|
|
List<String> service_sids = new List<String>()
|
|
|
|
{
|
|
|
|
"S-1-5-18", // NT AUTHORITY\SYSTEM
|
|
|
|
"S-1-5-19", // NT AUTHORITY\LocalService
|
|
|
|
"S-1-5-20" // NT AUTHORITY\NetworkService
|
|
|
|
};
|
|
|
|
|
|
|
|
GrantAccessToWindowStationAndDesktop(account);
|
|
|
|
string account_sid = account.ToString();
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
if (service_sids.Contains(account_sid))
|
|
|
|
{
|
|
|
|
// We are trying to become to a service account
|
|
|
|
IntPtr hToken = GetUserHandle();
|
|
|
|
if (hToken == IntPtr.Zero)
|
|
|
|
throw new Exception("Failed to get token for NT AUTHORITY\\SYSTEM");
|
|
|
|
|
|
|
|
IntPtr hTokenDup = IntPtr.Zero;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (!DuplicateTokenEx(
|
|
|
|
hToken,
|
|
|
|
TokenAccessLevels.MaximumAllowed,
|
|
|
|
IntPtr.Zero,
|
|
|
|
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
|
|
|
|
TOKEN_TYPE.TokenPrimary,
|
|
|
|
out hTokenDup))
|
|
|
|
{
|
|
|
|
throw new Win32Exception("Failed to duplicate the SYSTEM account token");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
CloseHandle(hToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
string lpszDomain = "NT AUTHORITY";
|
|
|
|
string lpszUsername = null;
|
|
|
|
switch (account_sid)
|
|
|
|
{
|
|
|
|
case "S-1-5-18":
|
|
|
|
tokens.Add(hTokenDup);
|
|
|
|
return tokens;
|
|
|
|
case "S-1-5-19":
|
|
|
|
lpszUsername = "LocalService";
|
|
|
|
break;
|
|
|
|
case "S-1-5-20":
|
|
|
|
lpszUsername = "NetworkService";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ImpersonateLoggedOnUser(hTokenDup))
|
|
|
|
throw new Win32Exception("Failed to impersonate as SYSTEM account");
|
|
|
|
|
|
|
|
IntPtr newToken = IntPtr.Zero;
|
|
|
|
if (!LogonUser(
|
|
|
|
lpszUsername,
|
|
|
|
lpszDomain,
|
|
|
|
null,
|
|
|
|
LogonType.LOGON32_LOGON_SERVICE,
|
|
|
|
LogonProvider.LOGON32_PROVIDER_DEFAULT,
|
|
|
|
out newToken))
|
|
|
|
{
|
|
|
|
throw new Win32Exception("LogonUser failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
RevertToSelf();
|
|
|
|
tokens.Add(newToken);
|
|
|
|
return tokens;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// We are trying to become a local or domain account
|
|
|
|
string domain = null;
|
|
|
|
if (username.Contains(@"\"))
|
|
|
|
{
|
|
|
|
var user_split = username.Split(Convert.ToChar(@"\"));
|
|
|
|
domain = user_split[0];
|
|
|
|
username = user_split[1];
|
|
|
|
}
|
|
|
|
else if (username.Contains("@"))
|
|
|
|
domain = null;
|
|
|
|
else
|
|
|
|
domain = ".";
|
|
|
|
|
|
|
|
// Logon and get the token
|
|
|
|
IntPtr hToken = IntPtr.Zero;
|
|
|
|
if (!LogonUser(
|
|
|
|
username,
|
|
|
|
domain,
|
|
|
|
password,
|
|
|
|
LogonType.LOGON32_LOGON_INTERACTIVE,
|
|
|
|
LogonProvider.LOGON32_PROVIDER_DEFAULT,
|
|
|
|
out hToken))
|
|
|
|
{
|
|
|
|
throw new Win32Exception("LogonUser failed");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the elevate token
|
|
|
|
IntPtr hTokenElevated = GetElevatedToken(hToken);
|
|
|
|
|
|
|
|
tokens.Add(hTokenElevated);
|
|
|
|
tokens.Add(hToken);
|
|
|
|
|
|
|
|
return tokens;
|
|
|
|
}
|
2017-08-16 01:55:17 +00:00
|
|
|
}
|
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
private static IntPtr GetUserHandle()
|
2017-02-17 08:09:56 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
uint array_byte_size = 1024 * sizeof(uint);
|
|
|
|
IntPtr[] pids = new IntPtr[1024];
|
|
|
|
uint bytes_copied;
|
|
|
|
|
|
|
|
if (!EnumProcesses(pids, array_byte_size, out bytes_copied))
|
|
|
|
{
|
|
|
|
throw new Win32Exception("Failed to enumerate processes");
|
|
|
|
}
|
|
|
|
// TODO: Handle if bytes_copied is larger than the array size and rerun EnumProcesses with larger array
|
|
|
|
uint num_processes = bytes_copied / sizeof(uint);
|
|
|
|
|
|
|
|
for (uint i = 0; i < num_processes; i++)
|
|
|
|
{
|
|
|
|
IntPtr hProcess = OpenProcess(ProcessAccessFlags.PROCESS_QUERY_INFORMATION, false, pids[i]);
|
|
|
|
if (hProcess != IntPtr.Zero)
|
|
|
|
{
|
|
|
|
IntPtr hToken = IntPtr.Zero;
|
|
|
|
// According to CreateProcessWithTokenW we require a token with
|
|
|
|
// TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
|
|
|
|
// Also add in TOKEN_IMPERSONATE so we can get an impersontated token
|
|
|
|
TokenAccessLevels desired_access = TokenAccessLevels.Query |
|
|
|
|
TokenAccessLevels.Duplicate |
|
|
|
|
TokenAccessLevels.AssignPrimary |
|
|
|
|
TokenAccessLevels.Impersonate;
|
|
|
|
|
|
|
|
// TODO: Find out why I can't see processes from Network Service and Local Service
|
|
|
|
if (OpenProcessToken(hProcess, desired_access, out hToken))
|
|
|
|
{
|
|
|
|
string sid = GetTokenUserSID(hToken);
|
|
|
|
if (sid == "S-1-5-18")
|
|
|
|
{
|
|
|
|
CloseHandle(hProcess);
|
|
|
|
return hToken;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandle(hToken);
|
|
|
|
}
|
|
|
|
CloseHandle(hProcess);
|
|
|
|
}
|
|
|
|
|
|
|
|
return IntPtr.Zero;
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
private static string GetTokenUserSID(IntPtr hToken)
|
2017-07-04 00:46:27 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
uint token_length;
|
|
|
|
string sid;
|
|
|
|
|
|
|
|
if (!GetTokenInformation(hToken, TokenInformationClass.TokenUser, IntPtr.Zero, 0, out token_length))
|
|
|
|
{
|
|
|
|
int last_err = Marshal.GetLastWin32Error();
|
|
|
|
if (last_err != 122) // ERROR_INSUFFICIENT_BUFFER
|
|
|
|
throw new Win32Exception(last_err, "Failed to get TokenUser length");
|
|
|
|
}
|
|
|
|
|
|
|
|
IntPtr token_information = Marshal.AllocHGlobal((int)token_length);
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (!GetTokenInformation(hToken, TokenInformationClass.TokenUser, token_information, token_length, out token_length))
|
|
|
|
throw new Win32Exception("Failed to get TokenUser information");
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
TOKEN_USER token_user = (TOKEN_USER)Marshal.PtrToStructure(token_information, typeof(TOKEN_USER));
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
if (!ConvertSidToStringSidW(token_user.User.Sid, out sid))
|
|
|
|
throw new Win32Exception("Failed to get user SID");
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
Marshal.FreeHGlobal(token_information);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sid;
|
2017-07-04 00:46:27 +00:00
|
|
|
}
|
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
private static void GetProcessOutput(StreamReader stdoutStream, StreamReader stderrStream, out string stdout, out string stderr)
|
|
|
|
{
|
|
|
|
var sowait = new EventWaitHandle(false, EventResetMode.ManualReset);
|
|
|
|
var sewait = new EventWaitHandle(false, EventResetMode.ManualReset);
|
|
|
|
string so = null, se = null;
|
|
|
|
ThreadPool.QueueUserWorkItem((s) =>
|
|
|
|
{
|
|
|
|
so = stdoutStream.ReadToEnd();
|
|
|
|
sowait.Set();
|
|
|
|
});
|
|
|
|
ThreadPool.QueueUserWorkItem((s) =>
|
|
|
|
{
|
|
|
|
se = stderrStream.ReadToEnd();
|
|
|
|
sewait.Set();
|
|
|
|
});
|
|
|
|
foreach (var wh in new WaitHandle[] { sowait, sewait })
|
|
|
|
wh.WaitOne();
|
|
|
|
stdout = so;
|
|
|
|
stderr = se;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static uint GetProcessExitCode(IntPtr processHandle)
|
2017-08-16 01:55:17 +00:00
|
|
|
{
|
2017-07-04 00:46:27 +00:00
|
|
|
new NativeWaitHandle(processHandle).WaitOne();
|
|
|
|
uint exitCode;
|
2017-08-16 01:55:17 +00:00
|
|
|
if (!GetExitCodeProcess(processHandle, out exitCode))
|
|
|
|
throw new Win32Exception("Error getting process exit code");
|
2017-07-04 00:46:27 +00:00
|
|
|
return exitCode;
|
|
|
|
}
|
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
private static IntPtr GetElevatedToken(IntPtr hToken)
|
2017-02-17 08:09:56 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
uint requestedLength;
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
IntPtr pTokenInfo = Marshal.AllocHGlobal(sizeof(int));
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
if (!GetTokenInformation(hToken, TokenInformationClass.TokenElevationType, pTokenInfo, sizeof(int), out requestedLength))
|
|
|
|
throw new Win32Exception("Unable to get TokenElevationType");
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
var tet = (TokenElevationType)Marshal.ReadInt32(pTokenInfo);
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
// we already have the best token we can get, just use it
|
|
|
|
if (tet != TokenElevationType.TokenElevationTypeLimited)
|
|
|
|
return hToken;
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
GetTokenInformation(hToken, TokenInformationClass.TokenLinkedToken, IntPtr.Zero, 0, out requestedLength);
|
2017-08-16 01:55:17 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
IntPtr pLinkedToken = Marshal.AllocHGlobal((int)requestedLength);
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
if (!GetTokenInformation(hToken, TokenInformationClass.TokenLinkedToken, pLinkedToken, requestedLength, out requestedLength))
|
|
|
|
throw new Win32Exception("Unable to get linked token");
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
IntPtr linkedToken = Marshal.ReadIntPtr(pLinkedToken);
|
2017-07-04 00:46:27 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
Marshal.FreeHGlobal(pLinkedToken);
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
return linkedToken;
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
Marshal.FreeHGlobal(pTokenInfo);
|
|
|
|
}
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
private static void GrantAccessToWindowStationAndDesktop(SecurityIdentifier account)
|
|
|
|
{
|
|
|
|
const int WindowStationAllAccess = 0x000f037f;
|
|
|
|
GrantAccess(account, GetProcessWindowStation(), WindowStationAllAccess);
|
|
|
|
const int DesktopRightsAllAccess = 0x000f01ff;
|
|
|
|
GrantAccess(account, GetThreadDesktop(GetCurrentThreadId()), DesktopRightsAllAccess);
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
private static void GrantAccess(SecurityIdentifier account, IntPtr handle, int accessMask)
|
2017-02-17 08:09:56 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
SafeHandle safeHandle = new NoopSafeHandle(handle);
|
|
|
|
GenericSecurity security =
|
|
|
|
new GenericSecurity(false, ResourceType.WindowObject, safeHandle, AccessControlSections.Access);
|
|
|
|
security.AddAccessRule(
|
|
|
|
new GenericAccessRule(account, accessMask, AccessControlType.Allow));
|
|
|
|
security.Persist(safeHandle, AccessControlSections.Access);
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private class GenericSecurity : NativeObjectSecurity
|
|
|
|
{
|
|
|
|
public GenericSecurity(bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested)
|
|
|
|
: base(isContainer, resType, objectHandle, sectionsRequested) { }
|
|
|
|
public new void Persist(SafeHandle handle, AccessControlSections includeSections) { base.Persist(handle, includeSections); }
|
|
|
|
public new void AddAccessRule(AccessRule rule) { base.AddAccessRule(rule); }
|
|
|
|
public override Type AccessRightType { get { throw new NotImplementedException(); } }
|
|
|
|
public override AccessRule AccessRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited,
|
2017-08-16 01:55:17 +00:00
|
|
|
InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
|
|
|
|
{ throw new NotImplementedException(); }
|
2017-02-17 08:09:56 +00:00
|
|
|
public override Type AccessRuleType { get { return typeof(AccessRule); } }
|
|
|
|
public override AuditRule AuditRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited,
|
2017-08-16 01:55:17 +00:00
|
|
|
InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags)
|
|
|
|
{ throw new NotImplementedException(); }
|
2017-02-17 08:09:56 +00:00
|
|
|
public override Type AuditRuleType { get { return typeof(AuditRule); } }
|
|
|
|
}
|
|
|
|
|
|
|
|
private class NoopSafeHandle : SafeHandle
|
|
|
|
{
|
|
|
|
public NoopSafeHandle(IntPtr handle) : base(handle, false) { }
|
|
|
|
public override bool IsInvalid { get { return false; } }
|
|
|
|
protected override bool ReleaseHandle() { return true; }
|
|
|
|
}
|
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
private class GenericAccessRule : AccessRule
|
2017-08-16 01:55:17 +00:00
|
|
|
{
|
2017-10-10 00:58:29 +00:00
|
|
|
public GenericAccessRule(IdentityReference identity, int accessMask, AccessControlType type) :
|
|
|
|
base(identity, accessMask, false, InheritanceFlags.None, PropagationFlags.None, type)
|
|
|
|
{ }
|
2017-07-04 00:46:27 +00:00
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
"@
|
|
|
|
|
|
|
|
$exec_wrapper = {
|
2017-10-10 00:58:29 +00:00
|
|
|
#Requires -Version 3.0
|
|
|
|
Set-StrictMode -Version 2
|
|
|
|
$DebugPreference = "Continue"
|
|
|
|
$ErrorActionPreference = "Stop"
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
Function ConvertTo-HashtableFromPsCustomObject($myPsObject) {
|
|
|
|
$output = @{}
|
|
|
|
$myPsObject | Get-Member -MemberType *Property | % {
|
|
|
|
$val = $myPsObject.($_.name)
|
|
|
|
if ($val -is [psobject]) {
|
|
|
|
$val = ConvertTo-HashtableFromPsCustomObject -myPsObject $val
|
|
|
|
}
|
|
|
|
$output.($_.name) = $val
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
2017-10-10 00:58:29 +00:00
|
|
|
return $output
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
# stream JSON including become_pw, ps_module_payload, bin_module_payload, become_payload, write_payload_path, preserve directives
|
|
|
|
# exec runspace, capture output, cleanup, return module output
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
$json_raw = [System.Console]::In.ReadToEnd()
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
If (-not $json_raw) {
|
|
|
|
Write-Error "no input given" -Category InvalidArgument
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
$payload = ConvertTo-HashtableFromPsCustomObject -myPsObject (ConvertFrom-Json $json_raw)
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
# TODO: handle binary modules
|
|
|
|
# TODO: handle persistence
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
$actions = $payload.actions
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
# pop 0th action as entrypoint
|
|
|
|
$entrypoint = $payload.($actions[0])
|
|
|
|
$payload.actions = $payload.actions[1..99]
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
$entrypoint = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($entrypoint))
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
# load the current action entrypoint as a module custom object with a Run method
|
|
|
|
$entrypoint = New-Module -ScriptBlock ([scriptblock]::Create($entrypoint)) -AsCustomObject
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
Set-Variable -Scope global -Name complex_args -Value $payload["module_args"] | Out-Null
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
# dynamically create/load modules
|
|
|
|
ForEach ($mod in $payload.powershell_modules.GetEnumerator()) {
|
|
|
|
$decoded_module = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($mod.Value))
|
|
|
|
New-Module -ScriptBlock ([scriptblock]::Create($decoded_module)) -Name $mod.Key | Import-Module -WarningAction SilentlyContinue | Out-Null
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
$output = $entrypoint.Run($payload)
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
Write-Output $output
|
2017-02-17 08:09:56 +00:00
|
|
|
} # end exec_wrapper
|
|
|
|
|
2017-04-06 08:34:30 +00:00
|
|
|
Function Dump-Error ($excep) {
|
|
|
|
$eo = @{failed=$true}
|
|
|
|
|
|
|
|
$eo.msg = $excep.Exception.Message
|
|
|
|
$eo.exception = $excep | Out-String
|
|
|
|
$host.SetShouldExit(1)
|
|
|
|
|
|
|
|
$eo | ConvertTo-Json -Depth 10
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
Function Run($payload) {
|
|
|
|
# NB: action popping handled inside subprocess wrapper
|
|
|
|
|
|
|
|
$username = $payload.become_user
|
|
|
|
$password = $payload.become_password
|
|
|
|
|
2017-07-04 00:46:27 +00:00
|
|
|
# FUTURE: convert to SafeHandle so we can stop ignoring warnings?
|
|
|
|
Add-Type -TypeDefinition $helper_def -Debug:$false -IgnoreWarnings
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
# NB: CreateProcessWithTokenW commandline maxes out at 1024 chars, must bootstrap via filesystem
|
2017-02-17 08:09:56 +00:00
|
|
|
$temp = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName() + ".ps1")
|
|
|
|
$exec_wrapper.ToString() | Set-Content -Path $temp
|
2017-07-04 00:46:27 +00:00
|
|
|
# allow (potentially unprivileged) target user access to the tempfile (NB: this likely won't work if traverse checking is enabled)
|
|
|
|
$acl = Get-Acl $temp
|
|
|
|
$acl.AddAccessRule($(New-Object System.Security.AccessControl.FileSystemAccessRule($username, "FullControl", "Allow")))
|
|
|
|
Set-Acl $temp $acl | Out-Null
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
Try {
|
|
|
|
$payload_string = $payload | ConvertTo-Json -Depth 99 -Compress
|
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
$lp_command_line = New-Object System.Text.StringBuilder @("powershell.exe -NonInteractive -NoProfile -ExecutionPolicy Bypass -File $temp")
|
|
|
|
$lp_current_directory = "$env:SystemRoot"
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
$result = [Ansible.BecomeUtil]::RunAsUser($username, $password, $lp_command_line, $lp_current_directory, $payload_string)
|
|
|
|
$stdout = $result.StandardOut
|
|
|
|
$stderr = $result.StandardError
|
|
|
|
$rc = $result.ExitCode
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-10-10 00:58:29 +00:00
|
|
|
[Console]::Out.WriteLine($stdout.Trim())
|
|
|
|
[Console]::Error.WriteLine($stderr.Trim())
|
|
|
|
} Catch {
|
2017-04-06 08:34:30 +00:00
|
|
|
$excep = $_
|
|
|
|
Dump-Error $excep
|
2017-10-10 00:58:29 +00:00
|
|
|
} Finally {
|
2017-02-17 08:09:56 +00:00
|
|
|
Remove-Item $temp -ErrorAction SilentlyContinue
|
|
|
|
}
|
2017-10-04 01:42:27 +00:00
|
|
|
$host.SetShouldExit($rc)
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
2017-10-10 00:58:29 +00:00
|
|
|
'''
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
async_wrapper = br'''
|
|
|
|
Set-StrictMode -Version 2
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
|
|
|
|
# build exec_wrapper encoded command
|
|
|
|
# start powershell with breakaway running exec_wrapper encodedcommand
|
|
|
|
# stream payload to powershell with normal exec, but normal exec writes results to resultfile instead of stdout/stderr
|
|
|
|
# return asyncresult to controller
|
|
|
|
|
|
|
|
$exec_wrapper = {
|
|
|
|
#Requires -Version 3.0
|
|
|
|
$DebugPreference = "Continue"
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
Set-StrictMode -Version 2
|
|
|
|
|
|
|
|
function ConvertTo-HashtableFromPsCustomObject ($myPsObject){
|
|
|
|
$output = @{};
|
|
|
|
$myPsObject | Get-Member -MemberType *Property | % {
|
|
|
|
$val = $myPsObject.($_.name);
|
|
|
|
If ($val -is [psobject]) {
|
|
|
|
$val = ConvertTo-HashtableFromPsCustomObject $val
|
|
|
|
}
|
|
|
|
$output.($_.name) = $val
|
|
|
|
}
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
# stream JSON including become_pw, ps_module_payload, bin_module_payload, become_payload, write_payload_path, preserve directives
|
|
|
|
# exec runspace, capture output, cleanup, return module output
|
|
|
|
|
|
|
|
$json_raw = [System.Console]::In.ReadToEnd()
|
|
|
|
|
|
|
|
If (-not $json_raw) {
|
|
|
|
Write-Error "no input given" -Category InvalidArgument
|
|
|
|
}
|
|
|
|
|
|
|
|
$payload = ConvertTo-HashtableFromPsCustomObject (ConvertFrom-Json $json_raw)
|
|
|
|
|
|
|
|
# TODO: handle binary modules
|
|
|
|
# TODO: handle persistence
|
|
|
|
|
|
|
|
$actions = $payload.actions
|
|
|
|
|
|
|
|
# pop 0th action as entrypoint
|
|
|
|
$entrypoint = $payload.($actions[0])
|
|
|
|
$payload.actions = $payload.actions[1..99]
|
|
|
|
|
|
|
|
$entrypoint = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($entrypoint))
|
|
|
|
|
|
|
|
# load the current action entrypoint as a module custom object with a Run method
|
|
|
|
$entrypoint = New-Module -ScriptBlock ([scriptblock]::Create($entrypoint)) -AsCustomObject
|
|
|
|
|
|
|
|
Set-Variable -Scope global -Name complex_args -Value $payload["module_args"] | Out-Null
|
|
|
|
|
|
|
|
# dynamically create/load modules
|
|
|
|
ForEach ($mod in $payload.powershell_modules.GetEnumerator()) {
|
|
|
|
$decoded_module = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($mod.Value))
|
2017-02-27 22:29:43 +00:00
|
|
|
New-Module -ScriptBlock ([scriptblock]::Create($decoded_module)) -Name $mod.Key | Import-Module -WarningAction SilentlyContinue | Out-Null
|
2017-02-17 08:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$output = $entrypoint.Run($payload)
|
|
|
|
|
|
|
|
Write-Output $output
|
|
|
|
|
|
|
|
} # end exec_wrapper
|
|
|
|
|
|
|
|
|
|
|
|
Function Run($payload) {
|
|
|
|
# BEGIN Ansible.Async native type definition
|
|
|
|
$native_process_util = @"
|
|
|
|
using Microsoft.Win32.SafeHandles;
|
|
|
|
using System;
|
|
|
|
using System.ComponentModel;
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
using System.Text;
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
namespace Ansible.Async {
|
|
|
|
|
|
|
|
public static class NativeProcessUtil
|
|
|
|
{
|
|
|
|
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, BestFitMapping=false)]
|
|
|
|
public static extern bool CreateProcess(
|
|
|
|
[MarshalAs(UnmanagedType.LPTStr)]
|
|
|
|
string lpApplicationName,
|
|
|
|
StringBuilder lpCommandLine,
|
|
|
|
IntPtr lpProcessAttributes,
|
|
|
|
IntPtr lpThreadAttributes,
|
|
|
|
bool bInheritHandles,
|
|
|
|
uint dwCreationFlags,
|
|
|
|
IntPtr lpEnvironment,
|
|
|
|
[MarshalAs(UnmanagedType.LPTStr)]
|
|
|
|
string lpCurrentDirectory,
|
2017-03-14 23:37:55 +00:00
|
|
|
STARTUPINFOEX lpStartupInfo,
|
2017-02-17 08:09:56 +00:00
|
|
|
out PROCESS_INFORMATION lpProcessInformation);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
|
|
public static extern uint SearchPath (
|
|
|
|
string lpPath,
|
|
|
|
string lpFileName,
|
|
|
|
string lpExtension,
|
|
|
|
int nBufferLength,
|
|
|
|
[MarshalAs (UnmanagedType.LPTStr)]
|
|
|
|
StringBuilder lpBuffer,
|
|
|
|
out IntPtr lpFilePart);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll")]
|
|
|
|
public static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError=true)]
|
|
|
|
public static extern IntPtr GetStdHandle(StandardHandleValues nStdHandle);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError=true)]
|
|
|
|
public static extern bool SetHandleInformation(IntPtr hObject, HandleFlags dwMask, int dwFlags);
|
|
|
|
|
2017-03-14 23:37:55 +00:00
|
|
|
[DllImport("kernel32.dll", SetLastError=true)]
|
|
|
|
public static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref int lpSize);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError=true)]
|
|
|
|
public static extern bool UpdateProcThreadAttribute(
|
|
|
|
IntPtr lpAttributeList,
|
|
|
|
uint dwFlags,
|
|
|
|
IntPtr Attribute,
|
|
|
|
IntPtr lpValue,
|
|
|
|
IntPtr cbSize,
|
|
|
|
IntPtr lpPreviousValue,
|
|
|
|
IntPtr lpReturnSize);
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
public static string SearchPath(string findThis)
|
|
|
|
{
|
|
|
|
StringBuilder sbOut = new StringBuilder(1024);
|
|
|
|
IntPtr filePartOut;
|
|
|
|
|
|
|
|
if(SearchPath(null, findThis, null, sbOut.Capacity, sbOut, out filePartOut) == 0)
|
|
|
|
throw new FileNotFoundException("Couldn't locate " + findThis + " on path");
|
|
|
|
|
|
|
|
return sbOut.ToString();
|
|
|
|
}
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError=true)]
|
|
|
|
static extern SafeFileHandle OpenThread(
|
|
|
|
ThreadAccessRights dwDesiredAccess,
|
|
|
|
bool bInheritHandle,
|
|
|
|
int dwThreadId);
|
|
|
|
|
|
|
|
[DllImport("kernel32.dll", SetLastError=true)]
|
|
|
|
static extern int ResumeThread(SafeHandle hThread);
|
|
|
|
|
|
|
|
public static void ResumeThreadById(int threadId)
|
|
|
|
{
|
|
|
|
var threadHandle = OpenThread(ThreadAccessRights.SUSPEND_RESUME, false, threadId);
|
|
|
|
if(threadHandle.IsInvalid)
|
|
|
|
throw new Exception(String.Format("Thread ID {0} is invalid ({1})", threadId,
|
|
|
|
new Win32Exception(Marshal.GetLastWin32Error()).Message));
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if(ResumeThread(threadHandle) == -1)
|
|
|
|
throw new Exception(String.Format("Thread ID {0} cannot be resumed ({1})", threadId,
|
|
|
|
new Win32Exception(Marshal.GetLastWin32Error()).Message));
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
threadHandle.Dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void ResumeProcessById(int pid)
|
|
|
|
{
|
|
|
|
var proc = Process.GetProcessById(pid);
|
|
|
|
|
|
|
|
// wait for at least one suspended thread in the process (this handles possible slow startup race where
|
|
|
|
// primary thread of created-suspended process has not yet become runnable)
|
|
|
|
var retryCount = 0;
|
|
|
|
while(!proc.Threads.OfType<ProcessThread>().Any(t=>t.ThreadState == System.Diagnostics.ThreadState.Wait &&
|
|
|
|
t.WaitReason == ThreadWaitReason.Suspended))
|
|
|
|
{
|
|
|
|
proc.Refresh();
|
|
|
|
Thread.Sleep(50);
|
|
|
|
if (retryCount > 100)
|
|
|
|
throw new InvalidOperationException(String.Format("No threads were suspended in target PID {0} after 5s", pid));
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach(var thread in proc.Threads.OfType<ProcessThread>().Where(t => t.ThreadState == System.Diagnostics.ThreadState.Wait &&
|
|
|
|
t.WaitReason == ThreadWaitReason.Suspended))
|
|
|
|
ResumeThreadById(thread.Id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
public class SECURITY_ATTRIBUTES
|
|
|
|
{
|
|
|
|
public int nLength;
|
|
|
|
public IntPtr lpSecurityDescriptor;
|
|
|
|
public bool bInheritHandle = false;
|
|
|
|
|
|
|
|
public SECURITY_ATTRIBUTES() {
|
|
|
|
nLength = Marshal.SizeOf(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
public class STARTUPINFO
|
|
|
|
{
|
|
|
|
public Int32 cb;
|
|
|
|
public IntPtr lpReserved;
|
|
|
|
public IntPtr lpDesktop;
|
|
|
|
public IntPtr lpTitle;
|
|
|
|
public Int32 dwX;
|
|
|
|
public Int32 dwY;
|
|
|
|
public Int32 dwXSize;
|
|
|
|
public Int32 dwYSize;
|
|
|
|
public Int32 dwXCountChars;
|
|
|
|
public Int32 dwYCountChars;
|
|
|
|
public Int32 dwFillAttribute;
|
|
|
|
public Int32 dwFlags;
|
|
|
|
public Int16 wShowWindow;
|
|
|
|
public Int16 cbReserved2;
|
|
|
|
public IntPtr lpReserved2;
|
|
|
|
public IntPtr hStdInput;
|
|
|
|
public IntPtr hStdOutput;
|
|
|
|
public IntPtr hStdError;
|
|
|
|
|
|
|
|
public STARTUPINFO() {
|
|
|
|
cb = Marshal.SizeOf(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-14 23:37:55 +00:00
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
public class STARTUPINFOEX {
|
|
|
|
public STARTUPINFO startupInfo;
|
|
|
|
public IntPtr lpAttributeList;
|
|
|
|
|
|
|
|
public STARTUPINFOEX() {
|
|
|
|
startupInfo = new STARTUPINFO();
|
|
|
|
startupInfo.cb = Marshal.SizeOf(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-17 08:09:56 +00:00
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
public struct PROCESS_INFORMATION
|
|
|
|
{
|
|
|
|
public IntPtr hProcess;
|
|
|
|
public IntPtr hThread;
|
|
|
|
public int dwProcessId;
|
|
|
|
public int dwThreadId;
|
|
|
|
}
|
|
|
|
|
|
|
|
[Flags]
|
|
|
|
enum ThreadAccessRights : uint
|
|
|
|
{
|
|
|
|
SUSPEND_RESUME = 0x0002
|
|
|
|
}
|
|
|
|
|
|
|
|
[Flags]
|
|
|
|
public enum StartupInfoFlags : uint
|
|
|
|
{
|
|
|
|
USESTDHANDLES = 0x00000100
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum StandardHandleValues : int
|
|
|
|
{
|
|
|
|
STD_INPUT_HANDLE = -10,
|
|
|
|
STD_OUTPUT_HANDLE = -11,
|
|
|
|
STD_ERROR_HANDLE = -12
|
|
|
|
}
|
|
|
|
|
|
|
|
[Flags]
|
|
|
|
public enum HandleFlags : uint
|
|
|
|
{
|
|
|
|
None = 0,
|
|
|
|
INHERIT = 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"@ # END Ansible.Async native type definition
|
|
|
|
|
|
|
|
# calculate the result path so we can include it in the worker payload
|
|
|
|
$jid = $payload.async_jid
|
|
|
|
$local_jid = $jid + "." + $pid
|
|
|
|
|
|
|
|
$results_path = [System.IO.Path]::Combine($env:LOCALAPPDATA, ".ansible_async", $local_jid)
|
|
|
|
|
|
|
|
$payload.async_results_path = $results_path
|
|
|
|
|
|
|
|
[System.IO.Directory]::CreateDirectory([System.IO.Path]::GetDirectoryName($results_path)) | Out-Null
|
|
|
|
|
2017-03-13 21:47:06 +00:00
|
|
|
Add-Type -TypeDefinition $native_process_util -Debug:$false
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
# FUTURE: create under new job to ensure all children die on exit?
|
|
|
|
|
2017-03-14 23:37:55 +00:00
|
|
|
# FUTURE: move these flags into C# enum?
|
2017-02-17 08:09:56 +00:00
|
|
|
# start process suspended + breakaway so we can record the watchdog pid without worrying about a completion race
|
|
|
|
Set-Variable CREATE_BREAKAWAY_FROM_JOB -Value ([uint32]0x01000000) -Option Constant
|
|
|
|
Set-Variable CREATE_SUSPENDED -Value ([uint32]0x00000004) -Option Constant
|
|
|
|
Set-Variable CREATE_UNICODE_ENVIRONMENT -Value ([uint32]0x000000400) -Option Constant
|
|
|
|
Set-Variable CREATE_NEW_CONSOLE -Value ([uint32]0x00000010) -Option Constant
|
2017-03-14 23:37:55 +00:00
|
|
|
Set-Variable EXTENDED_STARTUPINFO_PRESENT -Value ([uint32]0x00080000) -Option Constant
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-03-14 23:37:55 +00:00
|
|
|
$pstartup_flags = $CREATE_BREAKAWAY_FROM_JOB -bor $CREATE_UNICODE_ENVIRONMENT -bor $CREATE_NEW_CONSOLE `
|
|
|
|
-bor $CREATE_SUSPENDED -bor $EXTENDED_STARTUPINFO_PRESENT
|
2017-02-17 08:09:56 +00:00
|
|
|
|
2017-03-14 23:37:55 +00:00
|
|
|
# execute the dynamic watchdog as a breakway process to free us from the WinRM job, which will in turn exec the module
|
|
|
|
$si = New-Object Ansible.Async.STARTUPINFOEX
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
# setup stdin redirection, we'll leave stdout/stderr as normal
|
2017-03-14 23:37:55 +00:00
|
|
|
$si.startupInfo.dwFlags = [Ansible.Async.StartupInfoFlags]::USESTDHANDLES
|
|
|
|
$si.startupInfo.hStdOutput = [Ansible.Async.NativeProcessUtil]::GetStdHandle([Ansible.Async.StandardHandleValues]::STD_OUTPUT_HANDLE)
|
|
|
|
$si.startupInfo.hStdError = [Ansible.Async.NativeProcessUtil]::GetStdHandle([Ansible.Async.StandardHandleValues]::STD_ERROR_HANDLE)
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
$stdin_read = $stdin_write = 0
|
|
|
|
|
|
|
|
$pipesec = New-Object Ansible.Async.SECURITY_ATTRIBUTES
|
|
|
|
$pipesec.bInheritHandle = $true
|
|
|
|
|
|
|
|
If(-not [Ansible.Async.NativeProcessUtil]::CreatePipe([ref]$stdin_read, [ref]$stdin_write, $pipesec, 0)) {
|
|
|
|
throw "Stdin pipe setup failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
|
|
|
|
}
|
|
|
|
If(-not [Ansible.Async.NativeProcessUtil]::SetHandleInformation($stdin_write, [Ansible.Async.HandleFlags]::INHERIT, 0)) {
|
|
|
|
throw "Stdin handle setup failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
|
|
|
|
}
|
2017-03-14 23:37:55 +00:00
|
|
|
$si.startupInfo.hStdInput = $stdin_read
|
|
|
|
|
|
|
|
# create an attribute list with our explicit handle inheritance list to pass to CreateProcess
|
|
|
|
[int]$buf_sz = 0
|
|
|
|
|
|
|
|
# determine the buffer size necessary for our attribute list
|
|
|
|
If(-not [Ansible.Async.NativeProcessUtil]::InitializeProcThreadAttributeList([IntPtr]::Zero, 1, 0, [ref]$buf_sz)) {
|
|
|
|
$last_err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
|
|
|
|
If($last_err -ne 122) { # ERROR_INSUFFICIENT_BUFFER
|
|
|
|
throw "Attribute list size query failed, Win32Error: $last_err"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$si.lpAttributeList = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($buf_sz)
|
|
|
|
|
|
|
|
# initialize the attribute list
|
|
|
|
If(-not [Ansible.Async.NativeProcessUtil]::InitializeProcThreadAttributeList($si.lpAttributeList, 1, 0, [ref]$buf_sz)) {
|
|
|
|
throw "Attribute list init failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
|
|
|
|
}
|
|
|
|
|
|
|
|
$handles_to_inherit = [IntPtr[]]@($stdin_read)
|
|
|
|
$pinned_handles = [System.Runtime.InteropServices.GCHandle]::Alloc($handles_to_inherit, [System.Runtime.InteropServices.GCHandleType]::Pinned)
|
|
|
|
|
|
|
|
# update the attribute list with the handles we want to inherit
|
|
|
|
If(-not [Ansible.Async.NativeProcessUtil]::UpdateProcThreadAttribute($si.lpAttributeList, 0, 0x20002 <# PROC_THREAD_ATTRIBUTE_HANDLE_LIST #>, `
|
|
|
|
$pinned_handles.AddrOfPinnedObject(), [System.Runtime.InteropServices.Marshal]::SizeOf([type][IntPtr]) * $handles_to_inherit.Length, `
|
|
|
|
[System.IntPtr]::Zero, [System.IntPtr]::Zero)) {
|
|
|
|
throw "Attribute list update failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
|
|
|
|
}
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
# need to use a preamble-free version of UTF8Encoding
|
|
|
|
$utf8_encoding = New-Object System.Text.UTF8Encoding @($false)
|
|
|
|
$stdin_fs = New-Object System.IO.FileStream @($stdin_write, [System.IO.FileAccess]::Write, $true, 32768)
|
|
|
|
$stdin = New-Object System.IO.StreamWriter @($stdin_fs, $utf8_encoding, 32768)
|
|
|
|
|
|
|
|
$pi = New-Object Ansible.Async.PROCESS_INFORMATION
|
|
|
|
|
|
|
|
$encoded_command = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($exec_wrapper.ToString()))
|
|
|
|
|
|
|
|
# FUTURE: direct cmdline CreateProcess path lookup fails- this works but is sub-optimal
|
|
|
|
$exec_cmd = [Ansible.Async.NativeProcessUtil]::SearchPath("powershell.exe")
|
|
|
|
$exec_args = New-Object System.Text.StringBuilder @("`"$exec_cmd`" -NonInteractive -NoProfile -ExecutionPolicy Bypass -EncodedCommand $encoded_command")
|
|
|
|
|
|
|
|
# TODO: use proper Win32Exception + error
|
|
|
|
If(-not [Ansible.Async.NativeProcessUtil]::CreateProcess($exec_cmd, $exec_args,
|
|
|
|
[IntPtr]::Zero, [IntPtr]::Zero, $true, $pstartup_flags, [IntPtr]::Zero, $env:windir, $si, [ref]$pi)) {
|
|
|
|
#throw New-Object System.ComponentModel.Win32Exception
|
|
|
|
throw "Worker creation failed, Win32Error: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
|
|
|
|
}
|
|
|
|
|
|
|
|
# FUTURE: watch process for quick exit, capture stdout/stderr and return failure
|
|
|
|
|
|
|
|
$watchdog_pid = $pi.dwProcessId
|
|
|
|
|
|
|
|
[Ansible.Async.NativeProcessUtil]::ResumeProcessById($watchdog_pid)
|
|
|
|
|
|
|
|
# once process is resumed, we can send payload over stdin
|
|
|
|
$payload_string = $payload | ConvertTo-Json -Depth 99 -Compress
|
|
|
|
$stdin.WriteLine($payload_string)
|
|
|
|
$stdin.Close()
|
|
|
|
|
|
|
|
# populate initial results before we resume the process to avoid result race
|
|
|
|
$result = @{
|
|
|
|
started=1;
|
|
|
|
finished=0;
|
|
|
|
results_file=$results_path;
|
|
|
|
ansible_job_id=$local_jid;
|
|
|
|
_ansible_suppress_tmpdir_delete=$true;
|
|
|
|
ansible_async_watchdog_pid=$watchdog_pid
|
|
|
|
}
|
|
|
|
|
|
|
|
$result_json = ConvertTo-Json $result
|
|
|
|
Set-Content $results_path -Value $result_json
|
|
|
|
|
|
|
|
return $result_json
|
|
|
|
}
|
|
|
|
|
2017-06-02 11:14:11 +00:00
|
|
|
''' # end async_wrapper
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
async_watchdog = br'''
|
|
|
|
Set-StrictMode -Version 2
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
|
|
|
|
Add-Type -AssemblyName System.Web.Extensions
|
|
|
|
|
|
|
|
Function Log {
|
|
|
|
Param(
|
|
|
|
[string]$msg
|
|
|
|
)
|
|
|
|
|
|
|
|
If(Get-Variable -Name log_path -ErrorAction SilentlyContinue) {
|
|
|
|
Add-Content $log_path $msg
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Function Deserialize-Json {
|
|
|
|
Param(
|
|
|
|
[Parameter(ValueFromPipeline=$true)]
|
|
|
|
[string]$json
|
|
|
|
)
|
|
|
|
|
|
|
|
# FUTURE: move this into module_utils/powershell.ps1 and use for everything (sidestep PSCustomObject issues)
|
|
|
|
# FUTURE: won't work w/ Nano Server/.NET Core- fallback to DataContractJsonSerializer (which can't handle dicts on .NET 4.0)
|
|
|
|
|
|
|
|
Log "Deserializing:`n$json"
|
|
|
|
|
|
|
|
$jss = New-Object System.Web.Script.Serialization.JavaScriptSerializer
|
|
|
|
return $jss.DeserializeObject($json)
|
|
|
|
}
|
|
|
|
|
|
|
|
Function Write-Result {
|
|
|
|
Param(
|
|
|
|
[hashtable]$result,
|
|
|
|
[string]$resultfile_path
|
|
|
|
)
|
|
|
|
|
|
|
|
$result | ConvertTo-Json | Set-Content -Path $resultfile_path
|
|
|
|
}
|
|
|
|
|
|
|
|
Function Run($payload) {
|
|
|
|
$actions = $payload.actions
|
|
|
|
|
|
|
|
# pop 0th action as entrypoint
|
|
|
|
$entrypoint = $payload.($actions[0])
|
|
|
|
$entrypoint = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($entrypoint))
|
|
|
|
|
|
|
|
$payload.actions = $payload.actions[1..99]
|
|
|
|
|
|
|
|
$resultfile_path = $payload.async_results_path
|
|
|
|
$max_exec_time_sec = $payload.async_timeout_sec
|
|
|
|
|
|
|
|
Log "deserializing existing resultfile args"
|
|
|
|
# read in existing resultsfile to merge w/ module output (it should be written by the time we're unsuspended and running)
|
|
|
|
$result = Get-Content $resultfile_path -Raw | Deserialize-Json
|
|
|
|
|
|
|
|
Log "deserialized result is $($result | Out-String)"
|
|
|
|
|
|
|
|
Log "creating runspace"
|
|
|
|
|
|
|
|
$rs = [runspacefactory]::CreateRunspace()
|
|
|
|
$rs.Open()
|
|
|
|
|
|
|
|
Log "creating Powershell object"
|
|
|
|
|
|
|
|
$job = [powershell]::Create()
|
|
|
|
$job.Runspace = $rs
|
|
|
|
|
|
|
|
$job.AddScript($entrypoint) | Out-Null
|
|
|
|
$job.AddStatement().AddCommand("Run").AddArgument($payload) | Out-Null
|
|
|
|
|
|
|
|
Log "job BeginInvoke()"
|
|
|
|
|
|
|
|
$job_asyncresult = $job.BeginInvoke()
|
|
|
|
|
|
|
|
Log "waiting $max_exec_time_sec seconds for job to complete"
|
|
|
|
|
|
|
|
$signaled = $job_asyncresult.AsyncWaitHandle.WaitOne($max_exec_time_sec * 1000)
|
|
|
|
|
|
|
|
$result["finished"] = 1
|
|
|
|
|
|
|
|
If($job_asyncresult.IsCompleted) {
|
|
|
|
Log "job completed, calling EndInvoke()"
|
|
|
|
|
|
|
|
$job_output = $job.EndInvoke($job_asyncresult)
|
|
|
|
$job_error = $job.Streams.Error
|
|
|
|
|
|
|
|
Log "raw module stdout: \r\n$job_output"
|
|
|
|
If($job_error) {
|
|
|
|
Log "raw module stderr: \r\n$job_error"
|
|
|
|
}
|
|
|
|
|
|
|
|
# write success/output/error to result object
|
|
|
|
|
|
|
|
# TODO: cleanse leading/trailing junk
|
|
|
|
Try {
|
|
|
|
$module_result = Deserialize-Json $job_output
|
|
|
|
# TODO: check for conflicting keys
|
|
|
|
$result = $result + $module_result
|
|
|
|
}
|
|
|
|
Catch {
|
|
|
|
$excep = $_
|
|
|
|
|
|
|
|
$result.failed = $true
|
|
|
|
$result.msg = "failed to parse module output: $excep"
|
|
|
|
}
|
|
|
|
|
|
|
|
# TODO: determine success/fail, or always include stderr if nonempty?
|
|
|
|
Write-Result $result $resultfile_path
|
|
|
|
|
|
|
|
Log "wrote output to $resultfile_path"
|
|
|
|
}
|
|
|
|
Else {
|
|
|
|
$job.BeginStop($null, $null) | Out-Null # best effort stop
|
|
|
|
# write timeout to result object
|
|
|
|
$result.failed = $true
|
|
|
|
$result.msg = "timed out waiting for module completion"
|
|
|
|
Write-Result $result $resultfile_path
|
|
|
|
|
|
|
|
Log "wrote timeout to $resultfile_path"
|
|
|
|
}
|
|
|
|
|
|
|
|
# in the case of a hung pipeline, this will cause the process to stay alive until it's un-hung...
|
|
|
|
#$rs.Close() | Out-Null
|
|
|
|
}
|
|
|
|
|
2017-06-02 11:14:11 +00:00
|
|
|
''' # end async_watchdog
|
|
|
|
|
2016-09-07 05:54:17 +00:00
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
class ShellModule(object):
|
|
|
|
|
2016-02-02 19:46:09 +00:00
|
|
|
# Common shell filenames that this plugin handles
|
|
|
|
# Powershell is handled differently. It's selected when winrm is the
|
|
|
|
# connection
|
|
|
|
COMPATIBLE_SHELLS = frozenset()
|
|
|
|
# Family of shells this has. Must match the filename without extension
|
|
|
|
SHELL_FAMILY = 'powershell'
|
|
|
|
|
2016-09-06 20:00:21 +00:00
|
|
|
env = dict()
|
|
|
|
|
|
|
|
# We're being overly cautious about which keys to accept (more so than
|
|
|
|
# the Windows environment is capable of doing), since the powershell
|
|
|
|
# env provider's limitations don't appear to be documented.
|
|
|
|
safe_envkey = re.compile(r'^[\d\w_]{1,255}$')
|
|
|
|
|
2017-02-17 08:09:56 +00:00
|
|
|
# TODO: add binary module support
|
|
|
|
|
2016-09-06 20:00:21 +00:00
|
|
|
def assert_safe_env_key(self, key):
|
|
|
|
if not self.safe_envkey.match(key):
|
|
|
|
raise AnsibleError("Invalid PowerShell environment key: %s" % key)
|
|
|
|
return key
|
|
|
|
|
|
|
|
def safe_env_value(self, key, value):
|
|
|
|
if len(value) > 32767:
|
|
|
|
raise AnsibleError("PowerShell environment value for key '%s' exceeds 32767 characters in length" % key)
|
|
|
|
# powershell single quoted literals need single-quote doubling as their only escaping
|
|
|
|
value = value.replace("'", "''")
|
2016-09-07 05:54:17 +00:00
|
|
|
return to_text(value, errors='surrogate_or_strict')
|
2016-09-06 20:00:21 +00:00
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
def env_prefix(self, **kwargs):
|
2017-03-24 00:48:15 +00:00
|
|
|
# powershell/winrm env handling is handled in the exec wrapper
|
|
|
|
return ""
|
2014-11-14 22:14:08 +00:00
|
|
|
|
|
|
|
def join_path(self, *args):
|
2015-07-24 16:39:54 +00:00
|
|
|
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
|
2016-03-11 19:00:28 +00:00
|
|
|
return '\'%s\'' % path
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2016-01-11 17:07:05 +00:00
|
|
|
def get_remote_filename(self, pathname):
|
2016-05-11 20:14:01 +00:00
|
|
|
# powershell requires that script files end with .ps1
|
2016-01-11 17:07:05 +00:00
|
|
|
base_name = os.path.basename(pathname.strip())
|
|
|
|
name, ext = os.path.splitext(base_name.strip())
|
|
|
|
if ext.lower() not in ['.ps1', '.exe']:
|
|
|
|
return name + '.ps1'
|
2015-12-01 21:39:02 +00:00
|
|
|
|
|
|
|
return base_name.strip()
|
|
|
|
|
2014-11-14 22:14:08 +00:00
|
|
|
def path_has_trailing_slash(self, path):
|
|
|
|
# Allow Windows paths to be specified using either slash.
|
2015-07-24 16:39:54 +00:00
|
|
|
path = self._unquote(path)
|
2014-11-14 22:14:08 +00:00
|
|
|
return path.endswith('/') or path.endswith('\\')
|
|
|
|
|
2016-08-06 01:40:28 +00:00
|
|
|
def chmod(self, paths, mode):
|
2016-04-07 14:27:01 +00:00
|
|
|
raise NotImplementedError('chmod is not implemented for Powershell')
|
|
|
|
|
2016-08-06 01:40:28 +00:00
|
|
|
def chown(self, paths, user):
|
2016-04-07 14:27:01 +00:00
|
|
|
raise NotImplementedError('chown is not implemented for Powershell')
|
|
|
|
|
2016-08-06 01:40:28 +00:00
|
|
|
def set_user_facl(self, paths, user, mode):
|
2016-04-07 14:27:01 +00:00
|
|
|
raise NotImplementedError('set_user_facl is not implemented for Powershell')
|
2014-11-14 22:14:08 +00:00
|
|
|
|
|
|
|
def remove(self, path, recurse=False):
|
2015-07-24 16:39:54 +00:00
|
|
|
path = self._escape(self._unquote(path))
|
2014-11-14 22:14:08 +00:00
|
|
|
if recurse:
|
2015-04-27 05:28:25 +00:00
|
|
|
return self._encode_script('''Remove-Item "%s" -Force -Recurse;''' % path)
|
2014-11-14 22:14:08 +00:00
|
|
|
else:
|
2015-04-27 05:28:25 +00:00
|
|
|
return self._encode_script('''Remove-Item "%s" -Force;''' % path)
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2017-01-25 18:19:10 +00:00
|
|
|
def mkdtemp(self, basefile, system=False, mode=None, tmpdir=None):
|
2015-07-24 16:39:54 +00:00
|
|
|
basefile = self._escape(self._unquote(basefile))
|
2017-01-25 18:19:10 +00:00
|
|
|
# FIXME: Support system temp path and passed in tmpdir!
|
2015-04-27 05:28:25 +00:00
|
|
|
return self._encode_script('''(New-Item -Type Directory -Path $env:temp -Name "%s").FullName | Write-Host -Separator '';''' % basefile)
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2015-06-29 19:41:51 +00:00
|
|
|
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.
|
2015-07-24 16:39:54 +00:00
|
|
|
user_home_path = self._unquote(user_home_path)
|
2015-06-29 19:41:51 +00:00
|
|
|
if user_home_path == '~':
|
|
|
|
script = 'Write-Host (Get-Location).Path'
|
|
|
|
elif user_home_path.startswith('~\\'):
|
2015-07-24 16:39:54 +00:00
|
|
|
script = 'Write-Host ((Get-Location).Path + "%s")' % self._escape(user_home_path[1:])
|
2015-06-29 19:41:51 +00:00
|
|
|
else:
|
2015-07-24 16:39:54 +00:00
|
|
|
script = 'Write-Host "%s"' % self._escape(user_home_path)
|
2015-06-29 19:41:51 +00:00
|
|
|
return self._encode_script(script)
|
|
|
|
|
2016-03-25 15:13:44 +00:00
|
|
|
def exists(self, path):
|
|
|
|
path = self._escape(self._unquote(path))
|
|
|
|
script = '''
|
|
|
|
If (Test-Path "%s")
|
|
|
|
{
|
|
|
|
$res = 0;
|
|
|
|
}
|
|
|
|
Else
|
|
|
|
{
|
|
|
|
$res = 1;
|
|
|
|
}
|
|
|
|
Write-Host "$res";
|
|
|
|
Exit $res;
|
|
|
|
''' % path
|
|
|
|
return self._encode_script(script)
|
|
|
|
|
2015-06-29 19:41:51 +00:00
|
|
|
def checksum(self, path, *args, **kwargs):
|
2015-07-24 16:39:54 +00:00
|
|
|
path = self._escape(self._unquote(path))
|
2014-11-14 22:14:08 +00:00
|
|
|
script = '''
|
|
|
|
If (Test-Path -PathType Leaf "%(path)s")
|
|
|
|
{
|
2015-06-29 19:41:51 +00:00
|
|
|
$sp = new-object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider;
|
2014-11-14 22:14:08 +00:00
|
|
|
$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)
|
2015-04-27 05:28:25 +00:00
|
|
|
return self._encode_script(script)
|
2014-11-14 22:14:08 +00:00
|
|
|
|
2015-12-03 16:01:05 +00:00
|
|
|
def build_module_command(self, env_string, shebang, cmd, arg_path=None, rm_tmp=None):
|
2017-02-17 08:09:56 +00:00
|
|
|
# pipelining bypass
|
|
|
|
if cmd == '':
|
2017-06-27 05:58:09 +00:00
|
|
|
return '-'
|
2017-02-17 08:09:56 +00:00
|
|
|
|
|
|
|
# non-pipelining
|
|
|
|
|
2017-08-01 01:35:05 +00:00
|
|
|
cmd_parts = shlex.split(cmd, posix=False)
|
|
|
|
cmd_parts = list(map(to_text, cmd_parts))
|
2015-07-24 16:39:54 +00:00
|
|
|
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:])
|
2016-01-11 17:07:05 +00:00
|
|
|
elif not shebang:
|
|
|
|
# The module is assumed to be a binary
|
|
|
|
cmd_parts[0] = self._unquote(cmd_parts[0])
|
|
|
|
cmd_parts.append(arg_path)
|
2015-08-22 22:19:43 +00:00
|
|
|
script = '''
|
|
|
|
Try
|
|
|
|
{
|
|
|
|
%s
|
2016-09-06 20:00:21 +00:00
|
|
|
%s
|
2015-08-22 22:19:43 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
2016-09-06 20:00:21 +00:00
|
|
|
''' % (env_string, ' '.join(cmd_parts))
|
2014-11-14 22:14:08 +00:00
|
|
|
if rm_tmp:
|
2015-07-24 16:39:54 +00:00
|
|
|
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)
|
2017-01-16 19:21:48 +00:00
|
|
|
return self._encode_script(script, preserve_rc=False)
|
2015-04-27 05:28:25 +00:00
|
|
|
|
2017-02-17 08:09:56 +00:00
|
|
|
def wrap_for_exec(self, cmd):
|
|
|
|
return '& %s' % cmd
|
|
|
|
|
2015-07-24 16:39:54 +00:00
|
|
|
def _unquote(self, value):
|
|
|
|
'''Remove any matching quotes that wrap the given value.'''
|
2016-09-07 05:54:17 +00:00
|
|
|
value = to_text(value or '')
|
2015-07-24 16:39:54 +00:00
|
|
|
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
|
|
|
|
|
2015-04-27 05:28:25 +00:00
|
|
|
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]
|
2017-06-02 11:14:11 +00:00
|
|
|
|
|
|
|
def replace(m):
|
|
|
|
return substs[m.lastindex - 1]
|
|
|
|
|
2015-04-27 05:28:25 +00:00
|
|
|
return re.sub(pattern, replace, value)
|
|
|
|
|
2017-01-16 19:21:48 +00:00
|
|
|
def _encode_script(self, script, as_list=False, strict_mode=True, preserve_rc=True):
|
2015-04-27 05:28:25 +00:00
|
|
|
'''Convert a PowerShell script to a single base64-encoded command.'''
|
2016-09-07 05:54:17 +00:00
|
|
|
script = to_text(script)
|
2017-06-27 05:58:09 +00:00
|
|
|
|
|
|
|
if script == u'-':
|
|
|
|
cmd_parts = _common_args + ['-']
|
|
|
|
|
|
|
|
else:
|
|
|
|
if strict_mode:
|
|
|
|
script = u'Set-StrictMode -Version Latest\r\n%s' % script
|
|
|
|
# try to propagate exit code if present- won't work with begin/process/end-style scripts (ala put_file)
|
|
|
|
# NB: the exit code returned may be incorrect in the case of a successful command followed by an invalid command
|
|
|
|
if preserve_rc:
|
|
|
|
script = u'%s\r\nIf (-not $?) { If (Get-Variable LASTEXITCODE -ErrorAction SilentlyContinue) { exit $LASTEXITCODE } Else { exit 1 } }\r\n'\
|
|
|
|
% script
|
|
|
|
script = '\n'.join([x.strip() for x in script.splitlines() if x.strip()])
|
2017-08-01 01:35:05 +00:00
|
|
|
encoded_script = to_text(base64.b64encode(script.encode('utf-16-le')), 'utf-8')
|
2017-06-27 05:58:09 +00:00
|
|
|
cmd_parts = _common_args + ['-EncodedCommand', encoded_script]
|
|
|
|
|
2015-04-27 05:28:25 +00:00
|
|
|
if as_list:
|
|
|
|
return cmd_parts
|
|
|
|
return ' '.join(cmd_parts)
|