mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-06-21 07:40:22 -07:00
win_exec: refactor PS exec runner (#45334)
* win_exec: refactor PS exec runner * more changes for PSCore compatibility * made some changes based on the recent review * split up module exec scripts for smaller payload * removed C# module support to focus on just error msg improvement * cleaned up c# test classifier code
This commit is contained in:
parent
aa2f3edb49
commit
e972287c35
34 changed files with 2751 additions and 1676 deletions
721
lib/ansible/module_utils/csharp/Ansible.Become.cs
Normal file
721
lib/ansible/module_utils/csharp/Ansible.Become.cs
Normal file
|
@ -0,0 +1,721 @@
|
|||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
// TODO: make some classes/structs private/internal before the next release
|
||||
|
||||
namespace Ansible.Become
|
||||
{
|
||||
[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;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
|
||||
public byte[] _data1;
|
||||
public Int32 dwFlags;
|
||||
public Int16 wShowWindow;
|
||||
public Int16 cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public SafeFileHandle hStdInput;
|
||||
public SafeFileHandle hStdOutput;
|
||||
public SafeFileHandle hStdError;
|
||||
public STARTUPINFO()
|
||||
{
|
||||
cb = Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class STARTUPINFOEX
|
||||
{
|
||||
public STARTUPINFO startupInfo;
|
||||
public IntPtr lpAttributeList;
|
||||
public STARTUPINFOEX()
|
||||
{
|
||||
startupInfo = new STARTUPINFO();
|
||||
startupInfo.cb = Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public int dwProcessId;
|
||||
public int dwThreadId;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SID_AND_ATTRIBUTES
|
||||
{
|
||||
public IntPtr Sid;
|
||||
public int Attributes;
|
||||
}
|
||||
|
||||
public struct TOKEN_USER
|
||||
{
|
||||
public SID_AND_ATTRIBUTES User;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum StartupInfoFlags : uint
|
||||
{
|
||||
USESTDHANDLES = 0x00000100
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum CreationFlags : uint
|
||||
{
|
||||
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
|
||||
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
|
||||
CREATE_NEW_CONSOLE = 0x00000010,
|
||||
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,
|
||||
}
|
||||
|
||||
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_QUERY_INFORMATION = 0x00000400,
|
||||
}
|
||||
|
||||
public enum SECURITY_IMPERSONATION_LEVEL
|
||||
{
|
||||
SecurityImpersonation,
|
||||
}
|
||||
|
||||
public enum TOKEN_TYPE
|
||||
{
|
||||
TokenPrimary = 1,
|
||||
TokenImpersonation
|
||||
}
|
||||
|
||||
class NativeWaitHandle : WaitHandle
|
||||
{
|
||||
public NativeWaitHandle(IntPtr handle)
|
||||
{
|
||||
this.SafeWaitHandle = new SafeWaitHandle(handle, false);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_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); }
|
||||
}
|
||||
|
||||
public class CommandResult
|
||||
{
|
||||
public string StandardOut { get; internal set; }
|
||||
public string StandardError { get; internal set; }
|
||||
public uint ExitCode { get; internal set; }
|
||||
}
|
||||
|
||||
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 SafeFileHandle hReadPipe,
|
||||
out SafeFileHandle hWritePipe,
|
||||
SECURITY_ATTRIBUTES lpPipeAttributes,
|
||||
uint nSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool SetHandleInformation(
|
||||
SafeFileHandle 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)]
|
||||
private static extern bool ImpersonateLoggedOnUser(
|
||||
IntPtr hToken);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
private static extern bool RevertToSelf();
|
||||
|
||||
public static CommandResult RunAsUser(string username, string password, string lpCommandLine,
|
||||
string lpCurrentDirectory, string stdinInput, LogonFlags logonFlags, LogonType logonType)
|
||||
{
|
||||
SecurityIdentifier account = null;
|
||||
if (logonType != LogonType.LOGON32_LOGON_NEW_CREDENTIALS)
|
||||
{
|
||||
account = GetBecomeSid(username);
|
||||
}
|
||||
|
||||
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
|
||||
SafeFileHandle stdout_read, stdout_write, stderr_read, stderr_write, stdin_read, stdin_write;
|
||||
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, 32768);
|
||||
StreamWriter stdin = new StreamWriter(stdin_fs, utf8_encoding, 32768);
|
||||
|
||||
// Create the environment block if set
|
||||
IntPtr lpEnvironment = IntPtr.Zero;
|
||||
|
||||
CreationFlags startup_flags = CreationFlags.CREATE_UNICODE_ENVIRONMENT;
|
||||
|
||||
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
|
||||
|
||||
// Get the user tokens to try running processes with
|
||||
List<IntPtr> tokens = GetUserTokens(account, username, password, logonType);
|
||||
|
||||
bool launch_success = false;
|
||||
foreach (IntPtr token in tokens)
|
||||
{
|
||||
if (CreateProcessWithTokenW(
|
||||
token,
|
||||
logonFlags,
|
||||
null,
|
||||
new StringBuilder(lpCommandLine),
|
||||
startup_flags,
|
||||
lpEnvironment,
|
||||
lpCurrentDirectory,
|
||||
si,
|
||||
out pi))
|
||||
{
|
||||
launch_success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!launch_success)
|
||||
throw new Win32Exception("Failed to start become process");
|
||||
|
||||
CommandResult result = new CommandResult();
|
||||
// Setup the output buffers and get stdout/stderr
|
||||
FileStream stdout_fs = new FileStream(stdout_read, FileAccess.Read, 4096);
|
||||
StreamReader stdout = new StreamReader(stdout_fs, utf8_encoding, true, 4096);
|
||||
stdout_write.Close();
|
||||
|
||||
FileStream stderr_fs = new FileStream(stderr_read, FileAccess.Read, 4096);
|
||||
StreamReader stderr = new StreamReader(stderr_fs, utf8_encoding, true, 4096);
|
||||
stderr_write.Close();
|
||||
|
||||
stdin.WriteLine(stdinInput);
|
||||
stdin.Close();
|
||||
|
||||
string stdout_str, stderr_str = null;
|
||||
GetProcessOutput(stdout, stderr, out stdout_str, out stderr_str);
|
||||
UInt32 rc = GetProcessExitCode(pi.hProcess);
|
||||
|
||||
result.StandardOut = stdout_str;
|
||||
result.StandardError = stderr_str;
|
||||
result.ExitCode = rc;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static SecurityIdentifier GetBecomeSid(string username)
|
||||
{
|
||||
NTAccount account = new NTAccount(username);
|
||||
try
|
||||
{
|
||||
SecurityIdentifier security_identifier = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
|
||||
return security_identifier;
|
||||
}
|
||||
catch (IdentityNotMappedException ex)
|
||||
{
|
||||
throw new Exception(String.Format("Unable to find become user {0}: {1}", username, ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<IntPtr> GetUserTokens(SecurityIdentifier account, string username, string password, LogonType logonType)
|
||||
{
|
||||
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
|
||||
};
|
||||
|
||||
IntPtr hSystemToken = IntPtr.Zero;
|
||||
string account_sid = "";
|
||||
if (logonType != LogonType.LOGON32_LOGON_NEW_CREDENTIALS)
|
||||
{
|
||||
GrantAccessToWindowStationAndDesktop(account);
|
||||
// Try to get SYSTEM token handle so we can impersonate to get full admin token
|
||||
hSystemToken = GetSystemUserHandle();
|
||||
account_sid = account.ToString();
|
||||
}
|
||||
bool impersonated = false;
|
||||
|
||||
try
|
||||
{
|
||||
IntPtr hSystemTokenDup = IntPtr.Zero;
|
||||
if (hSystemToken == IntPtr.Zero && service_sids.Contains(account_sid))
|
||||
{
|
||||
// We need the SYSTEM token if we want to become one of those accounts, fail here
|
||||
throw new Win32Exception("Failed to get token for NT AUTHORITY\\SYSTEM");
|
||||
}
|
||||
else if (hSystemToken != IntPtr.Zero)
|
||||
{
|
||||
// We have the token, need to duplicate and impersonate
|
||||
bool dupResult = DuplicateTokenEx(
|
||||
hSystemToken,
|
||||
TokenAccessLevels.MaximumAllowed,
|
||||
IntPtr.Zero,
|
||||
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
|
||||
TOKEN_TYPE.TokenPrimary,
|
||||
out hSystemTokenDup);
|
||||
int lastError = Marshal.GetLastWin32Error();
|
||||
CloseHandle(hSystemToken);
|
||||
|
||||
if (!dupResult && service_sids.Contains(account_sid))
|
||||
throw new Win32Exception(lastError, "Failed to duplicate token for NT AUTHORITY\\SYSTEM");
|
||||
else if (dupResult && account_sid != "S-1-5-18")
|
||||
{
|
||||
if (ImpersonateLoggedOnUser(hSystemTokenDup))
|
||||
impersonated = true;
|
||||
else if (service_sids.Contains(account_sid))
|
||||
throw new Win32Exception("Failed to impersonate as SYSTEM account");
|
||||
}
|
||||
// If SYSTEM impersonation failed but we're trying to become a regular user, just proceed;
|
||||
// might get a limited token in UAC-enabled cases, but better than nothing...
|
||||
}
|
||||
|
||||
string domain = null;
|
||||
|
||||
if (service_sids.Contains(account_sid))
|
||||
{
|
||||
// We're using a well-known service account, do a service logon instead of the actual flag set
|
||||
logonType = LogonType.LOGON32_LOGON_SERVICE;
|
||||
domain = "NT AUTHORITY";
|
||||
password = null;
|
||||
switch (account_sid)
|
||||
{
|
||||
case "S-1-5-18":
|
||||
tokens.Add(hSystemTokenDup);
|
||||
return tokens;
|
||||
case "S-1-5-19":
|
||||
username = "LocalService";
|
||||
break;
|
||||
case "S-1-5-20":
|
||||
username = "NetworkService";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are trying to become a local or domain account
|
||||
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 = ".";
|
||||
}
|
||||
|
||||
IntPtr hToken = IntPtr.Zero;
|
||||
if (!LogonUser(
|
||||
username,
|
||||
domain,
|
||||
password,
|
||||
logonType,
|
||||
LogonProvider.LOGON32_PROVIDER_DEFAULT,
|
||||
out hToken))
|
||||
{
|
||||
throw new Win32Exception("LogonUser failed");
|
||||
}
|
||||
|
||||
if (!service_sids.Contains(account_sid))
|
||||
{
|
||||
// Try and get the elevated token for local/domain account
|
||||
IntPtr hTokenElevated = GetElevatedToken(hToken);
|
||||
tokens.Add(hTokenElevated);
|
||||
}
|
||||
|
||||
// add the original token as a fallback
|
||||
tokens.Add(hToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (impersonated)
|
||||
RevertToSelf();
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private static IntPtr GetSystemUserHandle()
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static string GetTokenUserSID(IntPtr hToken)
|
||||
{
|
||||
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");
|
||||
|
||||
TOKEN_USER token_user = (TOKEN_USER)Marshal.PtrToStructure(token_information, typeof(TOKEN_USER));
|
||||
|
||||
if (!ConvertSidToStringSidW(token_user.User.Sid, out sid))
|
||||
throw new Win32Exception("Failed to get user SID");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(token_information);
|
||||
}
|
||||
|
||||
return sid;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
new NativeWaitHandle(processHandle).WaitOne();
|
||||
uint exitCode;
|
||||
if (!GetExitCodeProcess(processHandle, out exitCode))
|
||||
throw new Win32Exception("Error getting process exit code");
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
private static IntPtr GetElevatedToken(IntPtr hToken)
|
||||
{
|
||||
uint requestedLength;
|
||||
|
||||
IntPtr pTokenInfo = Marshal.AllocHGlobal(sizeof(int));
|
||||
|
||||
try
|
||||
{
|
||||
if (!GetTokenInformation(hToken, TokenInformationClass.TokenElevationType, pTokenInfo, sizeof(int), out requestedLength))
|
||||
throw new Win32Exception("Unable to get TokenElevationType");
|
||||
|
||||
var tet = (TokenElevationType)Marshal.ReadInt32(pTokenInfo);
|
||||
|
||||
// we already have the best token we can get, just use it
|
||||
if (tet != TokenElevationType.TokenElevationTypeLimited)
|
||||
return hToken;
|
||||
|
||||
GetTokenInformation(hToken, TokenInformationClass.TokenLinkedToken, IntPtr.Zero, 0, out requestedLength);
|
||||
|
||||
IntPtr pLinkedToken = Marshal.AllocHGlobal((int)requestedLength);
|
||||
|
||||
if (!GetTokenInformation(hToken, TokenInformationClass.TokenLinkedToken, pLinkedToken, requestedLength, out requestedLength))
|
||||
throw new Win32Exception("Unable to get linked token");
|
||||
|
||||
IntPtr linkedToken = Marshal.ReadIntPtr(pLinkedToken);
|
||||
|
||||
Marshal.FreeHGlobal(pLinkedToken);
|
||||
|
||||
return linkedToken;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(pTokenInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GrantAccessToWindowStationAndDesktop(SecurityIdentifier account)
|
||||
{
|
||||
const int WindowStationAllAccess = 0x000f037f;
|
||||
GrantAccess(account, GetProcessWindowStation(), WindowStationAllAccess);
|
||||
const int DesktopRightsAllAccess = 0x000f01ff;
|
||||
GrantAccess(account, GetThreadDesktop(GetCurrentThreadId()), DesktopRightsAllAccess);
|
||||
}
|
||||
|
||||
private static void GrantAccess(SecurityIdentifier account, IntPtr handle, int accessMask)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
|
||||
{ throw new NotImplementedException(); }
|
||||
public override Type AccessRuleType { get { return typeof(AccessRule); } }
|
||||
public override AuditRule AuditRuleFactory(System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited,
|
||||
InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags)
|
||||
{ throw new NotImplementedException(); }
|
||||
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; }
|
||||
}
|
||||
|
||||
private class GenericAccessRule : AccessRule
|
||||
{
|
||||
public GenericAccessRule(IdentityReference identity, int accessMask, AccessControlType type) :
|
||||
base(identity, accessMask, false, InheritanceFlags.None, PropagationFlags.None, type)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
0
lib/ansible/module_utils/csharp/__init__.py
Normal file
0
lib/ansible/module_utils/csharp/__init__.py
Normal file
|
@ -0,0 +1,261 @@
|
|||
# Copyright (c) 2018 Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
Function Add-CSharpType {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Compiles one or more C# scripts similar to Add-Type. This exposes
|
||||
more configuration options that are useable within Ansible and it
|
||||
also allows multiple C# sources to be compiled together.
|
||||
|
||||
.PARAMETER References
|
||||
[String[]] A collection of C# scripts to compile together.
|
||||
|
||||
.PARAMETER IgnoreWarnings
|
||||
[Switch] Whether to compile code that contains compiler warnings, by
|
||||
default warnings will cause a compiler error.
|
||||
|
||||
.PARAMETER PassThru
|
||||
[Switch] Whether to return the loaded Assembly
|
||||
|
||||
.PARAMETER AnsibleModule
|
||||
TODO - This is an AnsibleModule object that is used to derive the
|
||||
TempPath and Debug values.
|
||||
TempPath is set to the TmpDir property of the class
|
||||
IncludeDebugInfo is set when the Ansible verbosity is >= 3
|
||||
|
||||
.PARAMETER TempPath
|
||||
[String] The temporary directory in which the dynamic assembly is
|
||||
compiled to. This file is deleted once compilation is complete.
|
||||
Cannot be used when AnsibleModule is set. This is a no-op when
|
||||
running on PSCore.
|
||||
|
||||
.PARAMETER IncludeDebugInfo
|
||||
[Switch] Whether to include debug information in the compiled
|
||||
assembly. Cannot be used when AnsibleModule is set. This is a no-op
|
||||
when running on PSCore.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][AllowEmptyCollection()][String[]]$References,
|
||||
[Switch]$IgnoreWarnings,
|
||||
[Switch]$PassThru,
|
||||
[Parameter(Mandatory=$true, ParameterSetName="Module")][Object]$AnsibleModule,
|
||||
[Parameter(ParameterSetName="Manual")][String]$TempPath = $env:TMP,
|
||||
[Parameter(ParameterSetName="Manual")][Switch]$IncludeDebugInfo
|
||||
)
|
||||
if ($null -eq $References -or $References.Length -eq 0) {
|
||||
return
|
||||
}
|
||||
|
||||
# define special symbols CORECLR, WINDOWS, UNIX if required
|
||||
# the Is* variables are defined on PSCore, if absent we assume an
|
||||
# older version of PowerShell under .NET Framework and Windows
|
||||
$defined_symbols = [System.Collections.ArrayList]@()
|
||||
$is_coreclr = Get-Variable -Name IsCoreCLR -ErrorAction SilentlyContinue
|
||||
if ($null -ne $is_coreclr) {
|
||||
if ($is_coreclr.Value) {
|
||||
$defined_symbols.Add("CORECLR") > $null
|
||||
}
|
||||
}
|
||||
$is_windows = Get-Variable -Name IsWindows -ErrorAction SilentlyContinue
|
||||
if ($null -ne $is_windows) {
|
||||
if ($is_windows.Value) {
|
||||
$defined_symbols.Add("WINDOWS") > $null
|
||||
} else {
|
||||
$defined_symbols.Add("UNIX") > $null
|
||||
}
|
||||
} else {
|
||||
$defined_symbols.Add("WINDOWS") > $null
|
||||
}
|
||||
|
||||
# pattern used to find referenced assemblies in the code
|
||||
$assembly_pattern = "^//\s*AssemblyReference\s+-Name\s+(?<Name>[\w.]*)(\s+-CLR\s+(?<CLR>Core|Framework))?$"
|
||||
|
||||
# PSCore vs PSDesktop use different methods to compile the code,
|
||||
# PSCore uses Roslyn and can compile the code purely in memory
|
||||
# without touching the disk while PSDesktop uses CodeDom and csc.exe
|
||||
# to compile the code. We branch out here and run each
|
||||
# distribution's method to add our C# code.
|
||||
if ($is_coreclr) {
|
||||
# compile the code using Roslyn on PSCore
|
||||
|
||||
# Include the default assemblies using the logic in Add-Type
|
||||
# https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs
|
||||
$assemblies = [System.Collections.Generic.HashSet`1[Microsoft.CodeAnalysis.MetadataReference]]@(
|
||||
[Microsoft.CodeAnalysis.CompilationReference]::CreateFromFile(([System.Reflection.Assembly]::GetAssembly([PSObject])).Location)
|
||||
)
|
||||
$netcore_app_ref_folder = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName([PSObject].Assembly.Location), "ref")
|
||||
foreach ($file in [System.IO.Directory]::EnumerateFiles($netcore_app_ref_folder, "*.dll", [System.IO.SearchOption]::TopDirectoryOnly)) {
|
||||
$assemblies.Add([Microsoft.CodeAnalysis.MetadataReference]::CreateFromFile($file)) > $null
|
||||
}
|
||||
|
||||
# loop through the references, parse as a SyntaxTree and get
|
||||
# referenced assemblies
|
||||
$parse_options = ([Microsoft.CodeAnalysis.CSharp.CSharpParseOptions]::Default).WithPreprocessorSymbols($defined_symbols)
|
||||
$syntax_trees = [System.Collections.Generic.List`1[Microsoft.CodeAnalysis.SyntaxTree]]@()
|
||||
foreach ($reference in $References) {
|
||||
# scan through code and add any assemblies that match
|
||||
# //AssemblyReference -Name ... [-CLR Core]
|
||||
$sr = New-Object -TypeName System.IO.StringReader -ArgumentList $reference
|
||||
try {
|
||||
while ($null -ne ($line = $sr.ReadLine())) {
|
||||
if ($line -imatch $assembly_pattern) {
|
||||
# verify the reference is not for .NET Framework
|
||||
if ($Matches.ContainsKey("CLR") -and $Matches.CLR -ne "Core") {
|
||||
continue
|
||||
}
|
||||
$assemblies.Add($Matches.Name) > $null
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$sr.Close()
|
||||
}
|
||||
$syntax_trees.Add([Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree]::ParseText($reference, $parse_options)) > $null
|
||||
}
|
||||
|
||||
# Release seems to contain the correct line numbers compared to
|
||||
# debug,may need to keep a closer eye on this in the future
|
||||
$compiler_options = (New-Object -TypeName Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions -ArgumentList @(
|
||||
[Microsoft.CodeAnalysis.OutputKind]::DynamicallyLinkedLibrary
|
||||
)).WithOptimizationLevel([Microsoft.CodeAnalysis.OptimizationLevel]::Release)
|
||||
|
||||
# set warnings to error out if IgnoreWarnings is not set
|
||||
if (-not $IgnoreWarnings.IsPresent) {
|
||||
$compiler_options = $compiler_options.WithGeneralDiagnosticOption([Microsoft.CodeAnalysis.ReportDiagnostic]::Error)
|
||||
}
|
||||
|
||||
# create compilation object
|
||||
$compilation = [Microsoft.CodeAnalysis.CSharp.CSharpCompilation]::Create(
|
||||
[System.Guid]::NewGuid().ToString(),
|
||||
$syntax_trees,
|
||||
$assemblies,
|
||||
$compiler_options
|
||||
)
|
||||
|
||||
# Load the compiled code and pdb info, we do this so we can
|
||||
# include line number in a stracktrace
|
||||
$code_ms = New-Object -TypeName System.IO.MemoryStream
|
||||
$pdb_ms = New-Object -TypeName System.IO.MemoryStream
|
||||
try {
|
||||
$emit_result = $compilation.Emit($code_ms, $pdb_ms)
|
||||
if (-not $emit_result.Success) {
|
||||
$errors = [System.Collections.ArrayList]@()
|
||||
|
||||
foreach ($e in $emit_result.Diagnostics) {
|
||||
# builds the error msg, based on logic in Add-Type
|
||||
# https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs#L1239
|
||||
if ($null -eq $e.Location.SourceTree) {
|
||||
$errors.Add($e.ToString()) > $null
|
||||
continue
|
||||
}
|
||||
|
||||
$cancel_token = New-Object -TypeName System.Threading.CancellationToken -ArgumentList $false
|
||||
$text_lines = $e.Location.SourceTree.GetText($cancel_token).Lines
|
||||
$line_span = $e.Location.GetLineSpan()
|
||||
|
||||
$diagnostic_message = $e.ToString()
|
||||
$error_line_string = $text_lines[$line_span.StartLinePosition.Line].ToString()
|
||||
$error_position = $line_span.StartLinePosition.Character
|
||||
|
||||
$sb = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($diagnostic_message.Length + $error_line_string.Length * 2 + 4)
|
||||
$sb.AppendLine($diagnostic_message)
|
||||
$sb.AppendLine($error_line_string)
|
||||
|
||||
for ($i = 0; $i -lt $error_line_string.Length; $i++) {
|
||||
if ([System.Char]::IsWhiteSpace($error_line_string[$i])) {
|
||||
continue
|
||||
}
|
||||
$sb.Append($error_line_string, 0, $i)
|
||||
$sb.Append(' ', [Math]::Max(0, $error_position - $i))
|
||||
$sb.Append("^")
|
||||
break
|
||||
}
|
||||
|
||||
$errors.Add($sb.ToString()) > $null
|
||||
}
|
||||
|
||||
throw [InvalidOperationException]"Failed to compile C# code:`r`n$($errors -join "`r`n")"
|
||||
}
|
||||
|
||||
$code_ms.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
|
||||
$pdb_ms.Seek(0, [System.IO.SeekOrigin]::Begin) > $null
|
||||
$compiled_assembly = [System.Runtime.Loader.AssemblyLoadContext]::Default.LoadFromStream($code_ms, $pdb_ms)
|
||||
} finally {
|
||||
$code_ms.Close()
|
||||
$pdb_ms.Close()
|
||||
}
|
||||
} else {
|
||||
# compile the code using CodeDom on PSDesktop
|
||||
|
||||
# configure compile options based on input
|
||||
if ($PSCmdlet.ParameterSetName -eq "Module") {
|
||||
$temp_path = $AnsibleModule.TmpDir
|
||||
$include_debug = $AnsibleModule.Verbosity -ge 3
|
||||
} else {
|
||||
$temp_path = $TempPath
|
||||
$include_debug = $IncludeDebugInfo.IsPresent
|
||||
}
|
||||
$compiler_options = [System.Collections.ArrayList]@("/optimize")
|
||||
if ($defined_symbols.Count -gt 0) {
|
||||
$compiler_options.Add("/define:" + ([String]::Join(";", $defined_symbols.ToArray()))) > $null
|
||||
}
|
||||
|
||||
$compile_parameters = New-Object -TypeName System.CodeDom.Compiler.CompilerParameters
|
||||
$compile_parameters.CompilerOptions = [String]::Join(" ", $compiler_options.ToArray())
|
||||
$compile_parameters.GenerateExecutable = $false
|
||||
$compile_parameters.GenerateInMemory = $true
|
||||
$compile_parameters.TreatWarningsAsErrors = (-not $IgnoreWarnings.IsPresent)
|
||||
$compile_parameters.IncludeDebugInformation = $include_debug
|
||||
$compile_parameters.TempFiles = (New-Object -TypeName System.CodeDom.Compiler.TempFileCollection -ArgumentList $temp_path, $false)
|
||||
|
||||
# Add-Type automatically references System.dll, System.Core.dll,
|
||||
# and System.Management.Automation.dll which we replicate here
|
||||
$assemblies = [System.Collections.Generic.HashSet`1[String]]@(
|
||||
"System.dll",
|
||||
"System.Core.dll",
|
||||
([System.Reflection.Assembly]::GetAssembly([PSObject])).Location
|
||||
)
|
||||
|
||||
# create a code snippet for each reference and check if we need
|
||||
# to reference any extra assemblies
|
||||
# //AssemblyReference -Name ... [-CLR Framework]
|
||||
$compile_units = [System.Collections.Generic.List`1[System.CodeDom.CodeSnippetCompileUnit]]@()
|
||||
foreach ($reference in $References) {
|
||||
$sr = New-Object -TypeName System.IO.StringReader -ArgumentList $reference
|
||||
try {
|
||||
while ($null -ne ($line = $sr.ReadLine())) {
|
||||
if ($line -imatch $assembly_pattern) {
|
||||
# verify the reference is not for .NET Core
|
||||
if ($Matches.ContainsKey("CLR") -and $Matches.CLR -ne "Framework") {
|
||||
continue
|
||||
}
|
||||
$assemblies.Add($Matches.Name) > $null
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$sr.Close()
|
||||
}
|
||||
$compile_units.Add((New-Object -TypeName System.CodeDom.CodeSnippetCompileUnit -ArgumentList $reference)) > $null
|
||||
}
|
||||
$compile_parameters.ReferencedAssemblies.AddRange($assemblies)
|
||||
|
||||
# compile the code together and check for errors
|
||||
$provider = New-Object -TypeName Microsoft.CSharp.CSharpCodeProvider
|
||||
$compile = $provider.CompileAssemblyFromDom($compile_parameters, $compile_units.ToArray())
|
||||
if ($compile.Errors.HasErrors) {
|
||||
$msg = "Failed to compile C# code: "
|
||||
foreach ($e in $compile.Errors) {
|
||||
$msg += "`r`n" + $e.ToString()
|
||||
}
|
||||
throw [InvalidOperationException]$msg
|
||||
}
|
||||
$compiled_assembly = $compile.CompiledAssembly
|
||||
}
|
||||
|
||||
# return the compiled assembly if PassThru is set.
|
||||
if ($PassThru) {
|
||||
return $compiled_assembly
|
||||
}
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Add-CSharpType
|
Loading…
Add table
Add a link
Reference in a new issue