mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
win become: refactor and add support for passwordless become (#48082)
* win become: refactor and add support for passwordless become * make tests more stable * fix up dep message for Load-CommandUtils * Add further check for System impersonation token * re-add support for become with accounts that have no password * doc fixes and slight code improvements * fix doc sanity issue
This commit is contained in:
parent
b3ac5b637a
commit
190d1ed7f1
13 changed files with 2586 additions and 1105 deletions
3
changelogs/fragments/win_become-passwordless.yaml
Normal file
3
changelogs/fragments/win_become-passwordless.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
minor_changes:
|
||||
- windows become - Add support for passwordless become.
|
||||
- windows become - Moved to shared C# util so modules can utilise the code.
|
|
@ -523,6 +523,42 @@ Because local service accounts do not have passwords, the
|
|||
``ansible_become_password`` parameter is not required and is ignored if
|
||||
specified.
|
||||
|
||||
Become without setting a Password
|
||||
---------------------------------
|
||||
|
||||
As of Ansible 2.8, ``become`` can be used to become a local or domain account
|
||||
without requiring a password for that account. For this method to work, the
|
||||
following requirements must be met:
|
||||
|
||||
* The connection user has the ``SeDebugPrivilege`` privilege assigned
|
||||
* The connection user is part of the ``BUILTIN\Administrators`` group
|
||||
* The ``become_user`` has either the ``SeBatchLogonRight`` or ``SeNetworkLogonRight`` user right
|
||||
|
||||
Using become without a password is achieved in one of two different methods:
|
||||
|
||||
* Duplicating an existing logon session's token if the account is already logged on
|
||||
* Using S4U to generate a logon token that is valid on the remote host only
|
||||
|
||||
In the first scenario, the become process is spawned from another logon of that
|
||||
user account. This could be an existing RDP logon, console logon, but this is
|
||||
not guaranteed to occur all the time. This is similar to the
|
||||
``Run only when user is logged on`` option for a Scheduled Task.
|
||||
|
||||
In the case where another logon of the become account does not exist, S4U is
|
||||
used to create a new logon and run the module through that. This is similar to
|
||||
the ``Run whether user is logged on or not`` with the ``Do not store password``
|
||||
option for a Scheduled Task. In this scenario, the become process will not be
|
||||
able to access any network resources like a normal WinRM process.
|
||||
|
||||
To make a distinction between using become with no password and becoming an
|
||||
account that has no password make sure to keep ``ansible_become_pass`` as
|
||||
undefined or set ``ansible_become_pass:``.
|
||||
|
||||
.. Note:: Because there are no guarantees an existing token will exist for a
|
||||
user when Ansible runs, there's a high change the become process will only
|
||||
have access to local resources. Use become with a password if the task needs
|
||||
to access network resources
|
||||
|
||||
Accounts without a Password
|
||||
---------------------------
|
||||
|
||||
|
@ -530,8 +566,7 @@ Accounts without a Password
|
|||
|
||||
Ansible can be used to become an account that does not have a password (like the
|
||||
``Guest`` account). To become an account without a password, set up the
|
||||
variables like normal but either do not define ``ansible_become_pass`` or set
|
||||
``ansible_become_pass: ''``.
|
||||
variables like normal but set ``ansible_become_pass: ''``.
|
||||
|
||||
Before become can work on an account like this, the local policy
|
||||
`Accounts: Limit local account use of blank passwords to console logon only <https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/jj852174(v=ws.11)>`_
|
||||
|
|
|
@ -5,6 +5,7 @@ param(
|
|||
[Parameter(Mandatory=$true)][System.Collections.IDictionary]$Payload
|
||||
)
|
||||
|
||||
#Requires -Module Ansible.ModuleUtils.AddType
|
||||
#AnsibleRequires -CSharpUtil Ansible.Become
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
@ -74,18 +75,24 @@ Function Get-BecomeFlags($flags) {
|
|||
}
|
||||
|
||||
Write-AnsibleLog "INFO - loading C# become code" "become_wrapper"
|
||||
$become_def = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Payload.csharp_utils["Ansible.Become"]))
|
||||
$add_type_b64 = $Payload.powershell_modules["Ansible.ModuleUtils.AddType"]
|
||||
$add_type = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($add_type_b64))
|
||||
New-Module -Name Ansible.ModuleUtils.AddType -ScriptBlock ([ScriptBlock]::Create($add_type)) | Import-Module > $null
|
||||
|
||||
# set the TMP env var to _ansible_remote_tmp to ensure the tmp binaries are
|
||||
# compiled to that location
|
||||
$new_tmp = [System.Environment]::ExpandEnvironmentVariables($Payload.module_args["_ansible_remote_tmp"])
|
||||
$old_tmp = $env:TMP
|
||||
$env:TMP = $new_tmp
|
||||
Add-Type -TypeDefinition $become_def -Debug:$false
|
||||
$env:TMP = $old_tmp
|
||||
$become_def = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Payload.csharp_utils["Ansible.Become"]))
|
||||
$process_def = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Payload.csharp_utils["Ansible.Process"]))
|
||||
Add-CSharpType -References $become_def, $process_def -TempPath $new_tmp -IncludeDebugInfo
|
||||
|
||||
$username = $Payload.become_user
|
||||
$password = $Payload.become_password
|
||||
# We need to set password to the value of NullString so a null password is preserved when crossing the .NET
|
||||
# boundary. If we pass $null it will automatically be converted to "" and we need to keep the distinction for
|
||||
# accounts that don't have a password and when someone wants to become without knowing the password.
|
||||
if ($null -eq $password) {
|
||||
$password = [NullString]::Value
|
||||
}
|
||||
|
||||
try {
|
||||
$logon_type, $logon_flags = Get-BecomeFlags -flags $Payload.become_flags
|
||||
} catch {
|
||||
|
@ -109,7 +116,7 @@ $bootstrap_wrapper = {
|
|||
&$exec_wrapper
|
||||
}
|
||||
$exec_command = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($bootstrap_wrapper.ToString()))
|
||||
$lp_command_line = New-Object System.Text.StringBuilder @("powershell.exe -NonInteractive -NoProfile -ExecutionPolicy Bypass -EncodedCommand $exec_command")
|
||||
$lp_command_line = "powershell.exe -NonInteractive -NoProfile -ExecutionPolicy Bypass -EncodedCommand $exec_command"
|
||||
$lp_current_directory = $env:SystemRoot # TODO: should this be set to the become user's profile dir?
|
||||
|
||||
# pop the become_wrapper action so we don't get stuck in a loop
|
||||
|
@ -124,8 +131,8 @@ $exec_wrapper += "`0`0`0`0" + $payload_json
|
|||
|
||||
try {
|
||||
Write-AnsibleLog "INFO - starting become process '$lp_command_line'" "become_wrapper"
|
||||
$result = [Ansible.Become.BecomeUtil]::RunAsUser($username, $password, $lp_command_line,
|
||||
$lp_current_directory, $exec_wrapper, $logon_flags, $logon_type)
|
||||
$result = [Ansible.Become.BecomeUtil]::CreateProcessAsUser($username, $password, $logon_flags, $logon_type,
|
||||
$null, $lp_command_line, $lp_current_directory, $null, $exec_wrapper)
|
||||
Write-AnsibleLog "INFO - become process complete with rc: $($result.ExitCode)" "become_wrapper"
|
||||
$stdout = $result.StandardOut
|
||||
try {
|
||||
|
|
File diff suppressed because it is too large
Load diff
445
lib/ansible/module_utils/csharp/Ansible.Process.cs
Normal file
445
lib/ansible/module_utils/csharp/Ansible.Process.cs
Normal file
|
@ -0,0 +1,445 @@
|
|||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ansible.Process
|
||||
{
|
||||
internal class NativeHelpers
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class SECURITY_ATTRIBUTES
|
||||
{
|
||||
public UInt32 nLength;
|
||||
public IntPtr lpSecurityDescriptor;
|
||||
public bool bInheritHandle = false;
|
||||
public SECURITY_ATTRIBUTES()
|
||||
{
|
||||
nLength = (UInt32)Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class STARTUPINFO
|
||||
{
|
||||
public UInt32 cb;
|
||||
public IntPtr lpReserved;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string lpDesktop;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string lpTitle;
|
||||
public UInt32 dwX;
|
||||
public UInt32 dwY;
|
||||
public UInt32 dwXSize;
|
||||
public UInt32 dwYSize;
|
||||
public UInt32 dwXCountChars;
|
||||
public UInt32 dwYCountChars;
|
||||
public UInt32 dwFillAttribute;
|
||||
public StartupInfoFlags dwFlags;
|
||||
public UInt16 wShowWindow;
|
||||
public UInt16 cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public SafeFileHandle hStdInput;
|
||||
public SafeFileHandle hStdOutput;
|
||||
public SafeFileHandle hStdError;
|
||||
public STARTUPINFO()
|
||||
{
|
||||
cb = (UInt32)Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class STARTUPINFOEX
|
||||
{
|
||||
public STARTUPINFO startupInfo;
|
||||
public IntPtr lpAttributeList;
|
||||
public STARTUPINFOEX()
|
||||
{
|
||||
startupInfo = new STARTUPINFO();
|
||||
startupInfo.cb = (UInt32)Marshal.SizeOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public int dwProcessId;
|
||||
public int dwThreadId;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ProcessCreationFlags : uint
|
||||
{
|
||||
CREATE_NEW_CONSOLE = 0x00000010,
|
||||
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
|
||||
EXTENDED_STARTUPINFO_PRESENT = 0x00080000
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum StartupInfoFlags : uint
|
||||
{
|
||||
USESTDHANDLES = 0x00000100
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum HandleFlags : uint
|
||||
{
|
||||
None = 0,
|
||||
INHERIT = 1
|
||||
}
|
||||
}
|
||||
|
||||
internal class NativeMethods
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool AllocConsole();
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true)]
|
||||
public static extern SafeMemoryBuffer CommandLineToArgvW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine,
|
||||
out int pNumArgs);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CreatePipe(
|
||||
out SafeFileHandle hReadPipe,
|
||||
out SafeFileHandle hWritePipe,
|
||||
NativeHelpers.SECURITY_ATTRIBUTES lpPipeAttributes,
|
||||
UInt32 nSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern bool CreateProcessW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
|
||||
StringBuilder lpCommandLine,
|
||||
IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
bool bInheritHandles,
|
||||
NativeHelpers.ProcessCreationFlags dwCreationFlags,
|
||||
SafeMemoryBuffer lpEnvironment,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
|
||||
NativeHelpers.STARTUPINFOEX lpStartupInfo,
|
||||
out NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr GetConsoleWindow();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool GetExitCodeProcess(
|
||||
SafeWaitHandle hProcess,
|
||||
out UInt32 lpExitCode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern uint SearchPathW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpPath,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpExtension,
|
||||
UInt32 nBufferLength,
|
||||
[MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpBuffer,
|
||||
out IntPtr lpFilePart);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool SetConsoleCP(
|
||||
UInt32 wCodePageID);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool SetConsoleOutputCP(
|
||||
UInt32 wCodePageID);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool SetHandleInformation(
|
||||
SafeFileHandle hObject,
|
||||
NativeHelpers.HandleFlags dwMask,
|
||||
NativeHelpers.HandleFlags dwFlags);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern UInt32 WaitForSingleObject(
|
||||
SafeWaitHandle hHandle,
|
||||
UInt32 dwMilliseconds);
|
||||
}
|
||||
|
||||
internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public SafeMemoryBuffer() : base(true) { }
|
||||
public SafeMemoryBuffer(int cb) : base(true)
|
||||
{
|
||||
base.SetHandle(Marshal.AllocHGlobal(cb));
|
||||
}
|
||||
public SafeMemoryBuffer(IntPtr handle) : base(true)
|
||||
{
|
||||
base.SetHandle(handle);
|
||||
}
|
||||
|
||||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
Marshal.FreeHGlobal(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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 Result
|
||||
{
|
||||
public string StandardOut { get; internal set; }
|
||||
public string StandardError { get; internal set; }
|
||||
public uint ExitCode { get; internal set; }
|
||||
}
|
||||
|
||||
public class ProcessUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses a command line string into an argv array according to the Windows rules
|
||||
/// </summary>
|
||||
/// <param name="lpCommandLine">The command line to parse</param>
|
||||
/// <returns>An array of arguments interpreted by Windows</returns>
|
||||
public static string[] ParseCommandLine(string lpCommandLine)
|
||||
{
|
||||
int numArgs;
|
||||
using (SafeMemoryBuffer buf = NativeMethods.CommandLineToArgvW(lpCommandLine, out numArgs))
|
||||
{
|
||||
if (buf.IsInvalid)
|
||||
throw new Win32Exception("Error parsing command line");
|
||||
IntPtr[] strptrs = new IntPtr[numArgs];
|
||||
Marshal.Copy(buf.DangerousGetHandle(), strptrs, 0, numArgs);
|
||||
return strptrs.Select(s => Marshal.PtrToStringUni(s)).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the path for the executable specified. Will throw a Win32Exception if the file is not found.
|
||||
/// </summary>
|
||||
/// <param name="lpFileName">The executable to search for</param>
|
||||
/// <returns>The full path of the executable to search for</returns>
|
||||
public static string SearchPath(string lpFileName)
|
||||
{
|
||||
StringBuilder sbOut = new StringBuilder(0);
|
||||
IntPtr filePartOut = IntPtr.Zero;
|
||||
UInt32 res = NativeMethods.SearchPathW(null, lpFileName, null, (UInt32)sbOut.Capacity, sbOut, out filePartOut);
|
||||
if (res == 0)
|
||||
{
|
||||
int lastErr = Marshal.GetLastWin32Error();
|
||||
if (lastErr == 2) // ERROR_FILE_NOT_FOUND
|
||||
throw new FileNotFoundException(String.Format("Could not find file '{0}'.", lpFileName));
|
||||
else
|
||||
throw new Win32Exception(String.Format("SearchPathW({0}) failed to get buffer length", lpFileName));
|
||||
}
|
||||
|
||||
sbOut.EnsureCapacity((int)res);
|
||||
if (NativeMethods.SearchPathW(null, lpFileName, null, (UInt32)sbOut.Capacity, sbOut, out filePartOut) == 0)
|
||||
throw new Win32Exception(String.Format("SearchPathW({0}) failed", lpFileName));
|
||||
|
||||
return sbOut.ToString();
|
||||
}
|
||||
|
||||
public static Result CreateProcess(string command)
|
||||
{
|
||||
return CreateProcess(null, command, null, null, String.Empty);
|
||||
}
|
||||
|
||||
public static Result CreateProcess(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory,
|
||||
IDictionary environment)
|
||||
{
|
||||
return CreateProcess(lpApplicationName, lpCommandLine, lpCurrentDirectory, environment, String.Empty);
|
||||
}
|
||||
|
||||
public static Result CreateProcess(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory,
|
||||
IDictionary environment, string stdin)
|
||||
{
|
||||
byte[] stdinBytes;
|
||||
if (String.IsNullOrEmpty(stdin))
|
||||
stdinBytes = new byte[0];
|
||||
else
|
||||
{
|
||||
if (!stdin.EndsWith(Environment.NewLine))
|
||||
stdin += Environment.NewLine;
|
||||
stdinBytes = new UTF8Encoding(false).GetBytes(stdin);
|
||||
}
|
||||
return CreateProcess(lpApplicationName, lpCommandLine, lpCurrentDirectory, environment, stdinBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a process based on the CreateProcess API call.
|
||||
/// </summary>
|
||||
/// <param name="lpApplicationName">The name of the executable or batch file to execute</param>
|
||||
/// <param name="lpCommandLine">The command line to execute, typically this includes lpApplication as the first argument</param>
|
||||
/// <param name="lpCurrentDirectory">The full path to the current directory for the process, null will have the same cwd as the calling process</param>
|
||||
/// <param name="environment">A dictionary of key/value pairs to define the new process environment</param>
|
||||
/// <param name="stdin">A byte array to send over the stdin pipe</param>
|
||||
/// <returns>Result object that contains the command output and return code</returns>
|
||||
public static Result CreateProcess(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory,
|
||||
IDictionary environment, byte[] stdin)
|
||||
{
|
||||
NativeHelpers.ProcessCreationFlags creationFlags = NativeHelpers.ProcessCreationFlags.CREATE_UNICODE_ENVIRONMENT |
|
||||
NativeHelpers.ProcessCreationFlags.EXTENDED_STARTUPINFO_PRESENT;
|
||||
NativeHelpers.PROCESS_INFORMATION pi = new NativeHelpers.PROCESS_INFORMATION();
|
||||
NativeHelpers.STARTUPINFOEX si = new NativeHelpers.STARTUPINFOEX();
|
||||
si.startupInfo.dwFlags = NativeHelpers.StartupInfoFlags.USESTDHANDLES;
|
||||
|
||||
SafeFileHandle stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinRead, stdinWrite;
|
||||
CreateStdioPipes(si, out stdoutRead, out stdoutWrite, out stderrRead, out stderrWrite, out stdinRead,
|
||||
out stdinWrite);
|
||||
FileStream stdinStream = new FileStream(stdinWrite, FileAccess.Write);
|
||||
|
||||
// $null from PowerShell ends up as an empty string, we need to convert back as an empty string doesn't
|
||||
// make sense for these parameters
|
||||
if (lpApplicationName == "")
|
||||
lpApplicationName = null;
|
||||
|
||||
if (lpCurrentDirectory == "")
|
||||
lpCurrentDirectory = null;
|
||||
|
||||
using (SafeMemoryBuffer lpEnvironment = CreateEnvironmentPointer(environment))
|
||||
{
|
||||
// Create console with utf-8 CP if no existing console is present
|
||||
bool isConsole = false;
|
||||
if (NativeMethods.GetConsoleWindow() == IntPtr.Zero)
|
||||
{
|
||||
isConsole = NativeMethods.AllocConsole();
|
||||
|
||||
// Set console input/output codepage to UTF-8
|
||||
NativeMethods.SetConsoleCP(65001);
|
||||
NativeMethods.SetConsoleOutputCP(65001);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
StringBuilder commandLine = new StringBuilder(lpCommandLine);
|
||||
if (!NativeMethods.CreateProcessW(lpApplicationName, commandLine, IntPtr.Zero, IntPtr.Zero,
|
||||
true, creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi))
|
||||
{
|
||||
throw new Win32Exception("CreateProcessW() failed");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (isConsole)
|
||||
NativeMethods.FreeConsole();
|
||||
}
|
||||
}
|
||||
|
||||
return WaitProcess(stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinStream, stdin, pi.hProcess);
|
||||
}
|
||||
|
||||
internal static void CreateStdioPipes(NativeHelpers.STARTUPINFOEX si, out SafeFileHandle stdoutRead,
|
||||
out SafeFileHandle stdoutWrite, out SafeFileHandle stderrRead, out SafeFileHandle stderrWrite,
|
||||
out SafeFileHandle stdinRead, out SafeFileHandle stdinWrite)
|
||||
{
|
||||
NativeHelpers.SECURITY_ATTRIBUTES pipesec = new NativeHelpers.SECURITY_ATTRIBUTES();
|
||||
pipesec.bInheritHandle = true;
|
||||
|
||||
if (!NativeMethods.CreatePipe(out stdoutRead, out stdoutWrite, pipesec, 0))
|
||||
throw new Win32Exception("STDOUT pipe setup failed");
|
||||
if (!NativeMethods.SetHandleInformation(stdoutRead, NativeHelpers.HandleFlags.INHERIT, 0))
|
||||
throw new Win32Exception("STDOUT pipe handle setup failed");
|
||||
|
||||
if (!NativeMethods.CreatePipe(out stderrRead, out stderrWrite, pipesec, 0))
|
||||
throw new Win32Exception("STDERR pipe setup failed");
|
||||
if (!NativeMethods.SetHandleInformation(stderrRead, NativeHelpers.HandleFlags.INHERIT, 0))
|
||||
throw new Win32Exception("STDERR pipe handle setup failed");
|
||||
|
||||
if (!NativeMethods.CreatePipe(out stdinRead, out stdinWrite, pipesec, 0))
|
||||
throw new Win32Exception("STDIN pipe setup failed");
|
||||
if (!NativeMethods.SetHandleInformation(stdinWrite, NativeHelpers.HandleFlags.INHERIT, 0))
|
||||
throw new Win32Exception("STDIN pipe handle setup failed");
|
||||
|
||||
si.startupInfo.hStdOutput = stdoutWrite;
|
||||
si.startupInfo.hStdError = stderrWrite;
|
||||
si.startupInfo.hStdInput = stdinRead;
|
||||
}
|
||||
|
||||
internal static SafeMemoryBuffer CreateEnvironmentPointer(IDictionary environment)
|
||||
{
|
||||
IntPtr lpEnvironment = IntPtr.Zero;
|
||||
if (environment != null && environment.Count > 0)
|
||||
{
|
||||
StringBuilder environmentString = new StringBuilder();
|
||||
foreach (DictionaryEntry kv in environment)
|
||||
environmentString.AppendFormat("{0}={1}\0", kv.Key, kv.Value);
|
||||
environmentString.Append('\0');
|
||||
|
||||
lpEnvironment = Marshal.StringToHGlobalUni(environmentString.ToString());
|
||||
}
|
||||
return new SafeMemoryBuffer(lpEnvironment);
|
||||
}
|
||||
|
||||
internal static Result WaitProcess(SafeFileHandle stdoutRead, SafeFileHandle stdoutWrite, SafeFileHandle stderrRead,
|
||||
SafeFileHandle stderrWrite, FileStream stdinStream, byte[] stdin, IntPtr hProcess)
|
||||
{
|
||||
// Setup the output buffers and get stdout/stderr
|
||||
UTF8Encoding utf8Encoding = new UTF8Encoding(false);
|
||||
FileStream stdoutFS = new FileStream(stdoutRead, FileAccess.Read, 4096);
|
||||
StreamReader stdout = new StreamReader(stdoutFS, utf8Encoding, true, 4096);
|
||||
stdoutWrite.Close();
|
||||
|
||||
FileStream stderrFS = new FileStream(stderrRead, FileAccess.Read, 4096);
|
||||
StreamReader stderr = new StreamReader(stderrFS, utf8Encoding, true, 4096);
|
||||
stderrWrite.Close();
|
||||
|
||||
stdinStream.Write(stdin, 0, stdin.Length);
|
||||
stdinStream.Close();
|
||||
|
||||
string stdoutStr, stderrStr = null;
|
||||
GetProcessOutput(stdout, stderr, out stdoutStr, out stderrStr);
|
||||
UInt32 rc = GetProcessExitCode(hProcess);
|
||||
|
||||
return new Result
|
||||
{
|
||||
StandardOut = stdoutStr,
|
||||
StandardError = stderrStr,
|
||||
ExitCode = rc
|
||||
};
|
||||
}
|
||||
|
||||
internal 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;
|
||||
}
|
||||
|
||||
internal static UInt32 GetProcessExitCode(IntPtr processHandle)
|
||||
{
|
||||
SafeWaitHandle hProcess = new SafeWaitHandle(processHandle, true);
|
||||
NativeMethods.WaitForSingleObject(hProcess, 0xFFFFFFFF);
|
||||
|
||||
UInt32 exitCode;
|
||||
if (!NativeMethods.GetExitCodeProcess(hProcess, out exitCode))
|
||||
throw new Win32Exception("GetExitCodeProcess() failed");
|
||||
return exitCode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,393 +1,43 @@
|
|||
# Copyright (c) 2017 Ansible Project
|
||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
$process_util = @"
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ansible
|
||||
{
|
||||
[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 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;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum StartupInfoFlags : uint
|
||||
{
|
||||
USESTDHANDLES = 0x00000100
|
||||
}
|
||||
|
||||
public enum HandleFlags : uint
|
||||
{
|
||||
None = 0,
|
||||
INHERIT = 1
|
||||
}
|
||||
|
||||
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 CommandUtil
|
||||
{
|
||||
private static UInt32 CREATE_UNICODE_ENVIRONMENT = 0x000000400;
|
||||
private static UInt32 EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false)]
|
||||
public static extern bool CreateProcess(
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
string lpApplicationName,
|
||||
StringBuilder lpCommandLine,
|
||||
IntPtr lpProcessAttributes,
|
||||
IntPtr lpThreadAttributes,
|
||||
bool bInheritHandles,
|
||||
uint dwCreationFlags,
|
||||
IntPtr lpEnvironment,
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
string lpCurrentDirectory,
|
||||
STARTUPINFOEX lpStartupInfo,
|
||||
out PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern bool CreatePipe(
|
||||
out SafeFileHandle hReadPipe,
|
||||
out SafeFileHandle hWritePipe,
|
||||
SECURITY_ATTRIBUTES lpPipeAttributes,
|
||||
uint nSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public 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, 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", SetLastError = true)]
|
||||
static extern IntPtr GetConsoleWindow();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool AllocConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool SetConsoleCP(
|
||||
UInt32 wCodePageID);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool SetConsoleOutputCP(
|
||||
UInt32 wCodePageID);
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true)]
|
||||
static extern IntPtr CommandLineToArgvW(
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
string lpCmdLine,
|
||||
out int pNumArgs);
|
||||
|
||||
public static string[] ParseCommandLine(string lpCommandLine)
|
||||
{
|
||||
int numArgs;
|
||||
IntPtr ret = CommandLineToArgvW(lpCommandLine, out numArgs);
|
||||
|
||||
if (ret == IntPtr.Zero)
|
||||
throw new Win32Exception("Error parsing command line");
|
||||
|
||||
IntPtr[] strptrs = new IntPtr[numArgs];
|
||||
Marshal.Copy(ret, strptrs, 0, numArgs);
|
||||
string[] cmdlineParts = strptrs.Select(s => Marshal.PtrToStringUni(s)).ToArray();
|
||||
|
||||
Marshal.FreeHGlobal(ret);
|
||||
|
||||
return cmdlineParts;
|
||||
}
|
||||
|
||||
public static string SearchPath(string lpFileName)
|
||||
{
|
||||
StringBuilder sbOut = new StringBuilder(1024);
|
||||
IntPtr filePartOut;
|
||||
|
||||
if (SearchPath(null, lpFileName, null, sbOut.Capacity, sbOut, out filePartOut) == 0)
|
||||
throw new FileNotFoundException(String.Format("Could not locate the following executable {0}", lpFileName));
|
||||
|
||||
return sbOut.ToString();
|
||||
}
|
||||
|
||||
public class CommandResult
|
||||
{
|
||||
public string StandardOut { get; internal set; }
|
||||
public string StandardError { get; internal set; }
|
||||
public uint ExitCode { get; internal set; }
|
||||
}
|
||||
|
||||
public static CommandResult RunCommand(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, string stdinInput, IDictionary environment)
|
||||
{
|
||||
UInt32 startup_flags = CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT;
|
||||
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);
|
||||
|
||||
// If lpCurrentDirectory is set to null in PS it will be an empty
|
||||
// string here, we need to convert it
|
||||
if (lpCurrentDirectory == "")
|
||||
lpCurrentDirectory = null;
|
||||
|
||||
StringBuilder environmentString = null;
|
||||
|
||||
if (environment != null && environment.Count > 0)
|
||||
{
|
||||
environmentString = new StringBuilder();
|
||||
foreach (DictionaryEntry kv in environment)
|
||||
environmentString.AppendFormat("{0}={1}\0", kv.Key, kv.Value);
|
||||
environmentString.Append('\0');
|
||||
}
|
||||
|
||||
// Create the environment block if set
|
||||
IntPtr lpEnvironment = IntPtr.Zero;
|
||||
if (environmentString != null)
|
||||
lpEnvironment = Marshal.StringToHGlobalUni(environmentString.ToString());
|
||||
|
||||
// Create console if needed to be inherited by child process
|
||||
bool isConsole = false;
|
||||
if (GetConsoleWindow() == IntPtr.Zero) {
|
||||
isConsole = AllocConsole();
|
||||
|
||||
// Set console input/output codepage to UTF-8
|
||||
SetConsoleCP(65001);
|
||||
SetConsoleOutputCP(65001);
|
||||
}
|
||||
|
||||
// Create new process and run
|
||||
StringBuilder argument_string = new StringBuilder(lpCommandLine);
|
||||
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
|
||||
if (!CreateProcess(
|
||||
lpApplicationName,
|
||||
argument_string,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
true,
|
||||
startup_flags,
|
||||
lpEnvironment,
|
||||
lpCurrentDirectory,
|
||||
si,
|
||||
out pi))
|
||||
{
|
||||
throw new Win32Exception("Failed to create new process");
|
||||
}
|
||||
|
||||
// Destroy console if we created it
|
||||
if (isConsole) {
|
||||
FreeConsole();
|
||||
}
|
||||
|
||||
// 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);
|
||||
uint rc = GetProcessExitCode(pi.hProcess);
|
||||
|
||||
return new CommandResult
|
||||
{
|
||||
StandardOut = stdout_str,
|
||||
StandardError = stderr_str,
|
||||
ExitCode = rc
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
#AnsibleRequires -CSharpUtil Ansible.Process
|
||||
|
||||
Function Load-CommandUtils {
|
||||
# makes the following static functions available
|
||||
# [Ansible.CommandUtil]::ParseCommandLine(string lpCommandLine)
|
||||
# [Ansible.CommandUtil]::SearchPath(string lpFileName)
|
||||
# [Ansible.CommandUtil]::RunCommand(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory, string stdinInput, string environmentBlock)
|
||||
#
|
||||
# there are also numerous P/Invoke methods that can be called if you are feeling adventurous
|
||||
|
||||
# FUTURE: find a better way to get the _ansible_remote_tmp variable
|
||||
$original_tmp = $env:TMP
|
||||
|
||||
$remote_tmp = $original_tmp
|
||||
$module_params = Get-Variable -Name complex_args -ErrorAction SilentlyContinue
|
||||
if ($module_params) {
|
||||
if ($module_params.Value.ContainsKey("_ansible_remote_tmp") ) {
|
||||
$remote_tmp = $module_params.Value["_ansible_remote_tmp"]
|
||||
$remote_tmp = [System.Environment]::ExpandEnvironmentVariables($remote_tmp)
|
||||
<#
|
||||
.SYNOPSIS
|
||||
No-op, as the C# types are automatically loaded.
|
||||
#>
|
||||
Param()
|
||||
$msg = "Load-CommandUtils is deprecated and no longer needed, this cmdlet will be removed in a future version"
|
||||
if ((Get-Command -Name Add-DeprecationWarning -ErrorAction SilentlyContinue) -and (Get-Variable -Name result -ErrorAction SilentlyContinue)) {
|
||||
Add-DeprecationWarning -obj $result.Value -message $msg -version 2.12
|
||||
} else {
|
||||
$module = Get-Variable -Name module -ErrorAction SilentlyContinue
|
||||
if ($null -ne $module -and $module.Value.GetType().FullName -eq "Ansible.Basic.AnsibleModule") {
|
||||
$module.Value.Deprecate($msg, "2.12")
|
||||
}
|
||||
}
|
||||
|
||||
$env:TMP = $remote_tmp
|
||||
Add-Type -TypeDefinition $process_util
|
||||
$env:TMP = $original_tmp
|
||||
}
|
||||
|
||||
Function Get-ExecutablePath($executable, $directory) {
|
||||
# lpApplicationName requires the full path to a file, we need to find it
|
||||
# ourselves.
|
||||
Function Get-ExecutablePath {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get's the full path to an executable, will search the directory specified or ones in the PATH env var.
|
||||
|
||||
.PARAMETER executable
|
||||
[String]The executable to seach for.
|
||||
|
||||
.PARAMETER directory
|
||||
[String] If set, the directory to search in.
|
||||
|
||||
.OUTPUT
|
||||
[String] The full path the executable specified.
|
||||
#>
|
||||
Param(
|
||||
[String]$executable,
|
||||
[String]$directory = $null
|
||||
)
|
||||
|
||||
# we need to add .exe if it doesn't have an extension already
|
||||
if (-not [System.IO.Path]::HasExtension($executable)) {
|
||||
|
@ -404,21 +54,41 @@ Function Get-ExecutablePath($executable, $directory) {
|
|||
if ($file -ne $null) {
|
||||
$executable_path = $file.FullName
|
||||
} else {
|
||||
$executable_path = [Ansible.CommandUtil]::SearchPath($executable)
|
||||
$executable_path = [Ansible.Process.ProcessUtil]::SearchPath($executable)
|
||||
}
|
||||
return $executable_path
|
||||
}
|
||||
|
||||
Function Run-Command {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Run a command with the CreateProcess API and return the stdout/stderr and return code.
|
||||
|
||||
.PARAMETER command
|
||||
The full command, including the executable, to run.
|
||||
|
||||
.PARAMETER working_directory
|
||||
The working directory to set on the new process, will default to the current working dir.
|
||||
|
||||
.PARAMETER stdin
|
||||
A string to sent over the stdin pipe to the new process.
|
||||
|
||||
.PARAMETER environment
|
||||
A hashtable of key/value pairs to run with the command. If set, it will replace all other env vars.
|
||||
|
||||
.OUTPUT
|
||||
[Hashtable]
|
||||
[String]executable - The full path to the executable that was run
|
||||
[String]stdout - The stdout stream of the process
|
||||
[String]stderr - The stderr stream of the process
|
||||
[Int32]rc - The return code of the process
|
||||
#>
|
||||
Param(
|
||||
[string]$command, # the full command to run including the executable
|
||||
[string]$working_directory = $null, # the working directory to run under, will default to the current dir
|
||||
[string]$stdin = $null, # a string to send to the stdin pipe when executing the command
|
||||
[hashtable]$environment = @{} # a hashtable of environment values to run the command under, this will replace all the other environment variables with these
|
||||
[string]$command,
|
||||
[string]$working_directory = $null,
|
||||
[string]$stdin = "",
|
||||
[hashtable]$environment = @{}
|
||||
)
|
||||
|
||||
# load the C# code we call in this function
|
||||
Load-CommandUtils
|
||||
|
||||
# need to validate the working directory if it is set
|
||||
if ($working_directory) {
|
||||
|
@ -430,11 +100,11 @@ Function Run-Command {
|
|||
|
||||
# lpApplicationName needs to be the full path to an executable, we do this
|
||||
# by getting the executable as the first arg and then getting the full path
|
||||
$arguments = [Ansible.CommandUtil]::ParseCommandLine($command)
|
||||
$arguments = [Ansible.Process.ProcessUtil]::ParseCommandLine($command)
|
||||
$executable = Get-ExecutablePath -executable $arguments[0] -directory $working_directory
|
||||
|
||||
# run the command and get the results
|
||||
$command_result = [Ansible.CommandUtil]::RunCommand($executable, $command, $working_directory, $stdin, $environment)
|
||||
$command_result = [Ansible.Process.ProcessUtil]::CreateProcess($executable, $command, $working_directory, $environment, $stdin)
|
||||
|
||||
return ,@{
|
||||
executable = $executable
|
||||
|
@ -445,4 +115,4 @@ Function Run-Command {
|
|||
}
|
||||
|
||||
# this line must stay at the bottom to ensure all defined module parts are exported
|
||||
Export-ModuleMember -Alias * -Function * -Cmdlet *
|
||||
Export-ModuleMember -Function Get-ExecutablePath, Load-CommandUtils, Run-Command
|
||||
|
|
|
@ -143,57 +143,6 @@
|
|||
- '"LogonUser failed" not in become_invalid_pass.msg'
|
||||
- '"Win32ErrorCode 1326)" not in become_invalid_pass.msg'
|
||||
|
||||
- name: test become with SYSTEM account
|
||||
win_whoami:
|
||||
become: yes
|
||||
become_method: runas
|
||||
become_user: SYSTEM
|
||||
register: whoami_out
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- whoami_out.account.sid == "S-1-5-18"
|
||||
- whoami_out.account.account_name == "SYSTEM"
|
||||
- whoami_out.account.domain_name == "NT AUTHORITY"
|
||||
- whoami_out.label.account_name == 'System Mandatory Level'
|
||||
- whoami_out.label.sid == 'S-1-16-16384'
|
||||
- whoami_out.logon_type == 'System'
|
||||
|
||||
- name: test become with NetworkService account
|
||||
win_whoami:
|
||||
become: yes
|
||||
become_method: runas
|
||||
become_user: NetworkService
|
||||
register: whoami_out
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- whoami_out.account.sid == "S-1-5-20"
|
||||
- whoami_out.account.account_name == "NETWORK SERVICE"
|
||||
- whoami_out.account.domain_name == "NT AUTHORITY"
|
||||
- whoami_out.label.account_name == 'System Mandatory Level'
|
||||
- whoami_out.label.sid == 'S-1-16-16384'
|
||||
- whoami_out.logon_type == 'Service'
|
||||
|
||||
- name: test become with LocalService account
|
||||
win_whoami:
|
||||
become: yes
|
||||
become_method: runas
|
||||
become_user: LocalService
|
||||
register: whoami_out
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- whoami_out.account.sid == "S-1-5-19"
|
||||
- whoami_out.account.account_name == "LOCAL SERVICE"
|
||||
- whoami_out.account.domain_name == "NT AUTHORITY"
|
||||
- whoami_out.label.account_name == 'System Mandatory Level'
|
||||
- whoami_out.label.sid == 'S-1-16-16384'
|
||||
- whoami_out.logon_type == 'Service'
|
||||
|
||||
- name: test become + async
|
||||
vars: *become_vars
|
||||
win_command: whoami
|
||||
|
@ -228,82 +177,6 @@
|
|||
register: failed_flags_invalid_flag
|
||||
failed_when: "failed_flags_invalid_flag.msg != \"internal error: failed to parse become_flags 'logon_flags=with_profile,invalid': become_flags logon_flags value 'invalid' is not valid, valid values are: with_profile, netcredentials_only\""
|
||||
|
||||
# Server 2008 doesn't work with network and network_cleartext, there isn't really a reason why you would want this anyway
|
||||
- name: check if we are running on a dinosaur, neanderthal or an OS of the modern age
|
||||
win_shell: |
|
||||
$version = [System.Environment]::OSVersion.Version
|
||||
if ($version -lt [Version]"6.1") {
|
||||
"dinosaur"
|
||||
} elseif ($version -lt [Version]"6.2") {
|
||||
"neanderthal"
|
||||
} else {
|
||||
"False"
|
||||
}
|
||||
register: os_version
|
||||
|
||||
- name: become different types
|
||||
vars: *become_vars
|
||||
win_whoami:
|
||||
become_flags: logon_type={{item.type}}
|
||||
register: become_logon_type
|
||||
when: not ((item.type == 'network' or item.type == 'network_cleartext') and os_version.stdout_lines[0] == "dinosaur")
|
||||
failed_when: become_logon_type.logon_type != item.actual and become_logon_type.sid != user_limited_result.sid
|
||||
with_items:
|
||||
- type: interactive
|
||||
actual: Interactive
|
||||
- type: batch
|
||||
actual: Batch
|
||||
- type: network
|
||||
actual: Network
|
||||
- type: network_cleartext
|
||||
actual: NetworkCleartext
|
||||
|
||||
- name: become netcredentials with network user
|
||||
vars:
|
||||
ansible_become_user: fakeuser
|
||||
ansible_become_password: fakepassword
|
||||
ansible_become_method: runas
|
||||
ansible_become: True
|
||||
ansible_become_flags: logon_type=new_credentials logon_flags=netcredentials_only
|
||||
win_whoami:
|
||||
register: become_netcredentials
|
||||
|
||||
- name: assert become netcredentials with network user
|
||||
assert:
|
||||
that:
|
||||
# new_credentials still come up as the ansible_user so we can't test that
|
||||
- become_netcredentials.label.account_name == 'High Mandatory Level'
|
||||
- become_netcredentials.label.sid == 'S-1-16-12288'
|
||||
|
||||
- name: become logon_flags bitwise tests when loading the profile
|
||||
# Error code of 2 means no file found == no profile loaded
|
||||
win_shell: |
|
||||
Add-Type -Name "Native" -Namespace "Ansible" -MemberDefinition '[DllImport("Userenv.dll", SetLastError=true)]public static extern bool GetProfileType(out UInt32 pdwFlags);'
|
||||
$profile_type = $null
|
||||
$res = [Ansible.Native]::GetProfileType([ref]$profile_type)
|
||||
if (-not $res) {
|
||||
$last_err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
|
||||
if ($last_err -eq 2) {
|
||||
return $false
|
||||
} else {
|
||||
throw [System.ComponentModel.Win32Exception]$last_err
|
||||
}
|
||||
} else {
|
||||
return $true
|
||||
}
|
||||
vars: *admin_become_vars
|
||||
become_flags: logon_flags={{item.flags}}
|
||||
register: become_logon_flags
|
||||
failed_when: become_logon_flags.stdout_lines[0]|bool != item.actual
|
||||
when: os_version.stdout_lines[0] not in ["dinosaur", "neanderthal"] # usual suspect 2008 doesn't support the no profile flags
|
||||
with_items:
|
||||
- flags:
|
||||
actual: False
|
||||
- flags: netcredentials_only
|
||||
actual: False
|
||||
- flags: with_profile,netcredentials_only
|
||||
actual: True
|
||||
|
||||
- name: echo some non ascii characters
|
||||
win_command: cmd.exe /c echo über den Fußgängerübergang gehen
|
||||
vars: *become_vars
|
||||
|
@ -348,7 +221,7 @@
|
|||
win_user:
|
||||
name: "{{ become_test_username }}"
|
||||
state: absent
|
||||
|
||||
|
||||
- name: ensure privileged test user is deleted
|
||||
win_user:
|
||||
name: "{{ become_test_admin_username }}"
|
||||
|
@ -360,7 +233,7 @@
|
|||
args:
|
||||
executable: cmd.exe
|
||||
when: become_test_username in profile_dir_out.stdout_lines[0]
|
||||
|
||||
|
||||
- name: ensure privileged test user profile is deleted
|
||||
# NB: have to work around powershell limitation of long filenames until win_file fixes it
|
||||
win_shell: rmdir /S /Q {{ admin_profile_dir_out.stdout_lines[0] }}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
- cmdout is not changed
|
||||
- cmdout.cmd == 'bogus_command1234'
|
||||
- cmdout.rc == 2
|
||||
- "'Could not locate the following executable bogus_command1234' in cmdout.msg"
|
||||
- "\"Could not find file 'bogus_command1234.exe'.\" in cmdout.msg"
|
||||
|
||||
- name: execute something with error output
|
||||
win_command: cmd /c "echo some output & echo some error 1>&2"
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,229 @@
|
|||
#!powershell
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#AnsibleRequires -CSharpUtil Ansible.Process
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, @{})
|
||||
|
||||
Function Assert-Equals {
|
||||
param(
|
||||
[Parameter(Mandatory=$true, ValueFromPipeline=$true)][AllowNull()]$Actual,
|
||||
[Parameter(Mandatory=$true, Position=0)][AllowNull()]$Expected
|
||||
)
|
||||
|
||||
$matched = $false
|
||||
if ($Actual -is [System.Collections.ArrayList] -or $Actual -is [Array]) {
|
||||
$Actual.Count | Assert-Equals -Expected $Expected.Count
|
||||
for ($i = 0; $i -lt $Actual.Count; $i++) {
|
||||
$actual_value = $Actual[$i]
|
||||
$expected_value = $Expected[$i]
|
||||
Assert-Equals -Actual $actual_value -Expected $expected_value
|
||||
}
|
||||
$matched = $true
|
||||
} else {
|
||||
$matched = $Actual -ceq $Expected
|
||||
}
|
||||
|
||||
if (-not $matched) {
|
||||
if ($Actual -is [PSObject]) {
|
||||
$Actual = $Actual.ToString()
|
||||
}
|
||||
|
||||
$call_stack = (Get-PSCallStack)[1]
|
||||
$module.Result.test = $test
|
||||
$module.Result.actual = $Actual
|
||||
$module.Result.expected = $Expected
|
||||
$module.Result.line = $call_stack.ScriptLineNumber
|
||||
$module.Result.method = $call_stack.Position.Text
|
||||
$module.FailJson("AssertionError: actual != expected")
|
||||
}
|
||||
}
|
||||
|
||||
$tests = @{
|
||||
"ParseCommandLine empty string" = {
|
||||
$expected = @((Get-Process -Id $pid).Path)
|
||||
$actual = [Ansible.Process.ProcessUtil]::ParseCommandLine("")
|
||||
Assert-Equals -Actual $actual -Expected $expected
|
||||
}
|
||||
|
||||
"ParseCommandLine single argument" = {
|
||||
$expected = @("powershell.exe")
|
||||
$actual = [Ansible.Process.ProcessUtil]::ParseCommandLine("powershell.exe")
|
||||
Assert-Equals -Actual $actual -Expected $expected
|
||||
}
|
||||
|
||||
"ParseCommandLine multiple arguments" = {
|
||||
$expected = @("powershell.exe", "-File", "C:\temp\script.ps1")
|
||||
$actual = [Ansible.Process.ProcessUtil]::ParseCommandLine("powershell.exe -File C:\temp\script.ps1")
|
||||
Assert-Equals -Actual $actual -Expected $expected
|
||||
}
|
||||
|
||||
"ParseCommandLine comples arguments" = {
|
||||
$expected = @('abc', 'd', 'ef gh', 'i\j', 'k"l', 'm\n op', 'ADDLOCAL=qr, s', 'tuv\', 'w''x', 'yz')
|
||||
$actual = [Ansible.Process.ProcessUtil]::ParseCommandLine('abc d "ef gh" i\j k\"l m\\"n op" ADDLOCAL="qr, s" tuv\ w''x yz')
|
||||
Assert-Equals -Actual $actual -Expected $expected
|
||||
}
|
||||
|
||||
"SearchPath normal" = {
|
||||
$expected = "$($env:SystemRoot)\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
$actual = [Ansible.Process.ProcessUtil]::SearchPath("powershell.exe")
|
||||
$actual | Assert-Equals -Expected $expected
|
||||
}
|
||||
|
||||
"SearchPath missing" = {
|
||||
$failed = $false
|
||||
try {
|
||||
[Ansible.Process.ProcessUtil]::SearchPath("fake.exe")
|
||||
} catch {
|
||||
$failed = $true
|
||||
$_.Exception.InnerException.GetType().FullName | Assert-Equals -Expected "System.IO.FileNotFoundException"
|
||||
$expected = 'Exception calling "SearchPath" with "1" argument(s): "Could not find file ''fake.exe''."'
|
||||
$_.Exception.Message | Assert-Equals -Expected $expected
|
||||
}
|
||||
$failed | Assert-Equals -Expected $true
|
||||
}
|
||||
|
||||
"CreateProcess basic" = {
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess("whoami.exe")
|
||||
$actual.GetType().FullName | Assert-Equals -Expected "Ansible.Process.Result"
|
||||
$actual.StandardOut | Assert-Equals -Expected "$(&whoami.exe)`r`n"
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess stderr" = {
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess("powershell.exe [System.Console]::Error.WriteLine('hi')")
|
||||
$actual.StandardOut | Assert-Equals -Expected ""
|
||||
$actual.StandardError | Assert-Equals -Expected "hi`r`n"
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess exit code" = {
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess("powershell.exe exit 10")
|
||||
$actual.StandardOut | Assert-Equals -Expected ""
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 10
|
||||
}
|
||||
|
||||
"CreateProcess bad executable" = {
|
||||
$failed = $false
|
||||
try {
|
||||
[Ansible.Process.ProcessUtil]::CreateProcess("fake.exe")
|
||||
} catch {
|
||||
$failed = $true
|
||||
$_.Exception.InnerException.GetType().FullName | Assert-Equals -Expected "Ansible.Process.Win32Exception"
|
||||
$expected = 'Exception calling "CreateProcess" with "1" argument(s): "CreateProcessW() failed '
|
||||
$expected += '(The system cannot find the file specified, Win32ErrorCode 2)"'
|
||||
$_.Exception.Message | Assert-Equals -Expected $expected
|
||||
}
|
||||
$failed | Assert-Equals -Expected $true
|
||||
}
|
||||
|
||||
"CreateProcess with unicode" = {
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess("cmd.exe /c echo 💩 café")
|
||||
$actual.StandardOut | Assert-Equals -Expected "💩 café`r`n"
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($null, "cmd.exe /c echo 💩 café", $null, $null)
|
||||
$actual.StandardOut | Assert-Equals -Expected "💩 café`r`n"
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess without working dir" = {
|
||||
$expected = $pwd.Path + "`r`n"
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($null, 'powershell.exe $pwd.Path', $null, $null)
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess with working dir" = {
|
||||
$expected = "C:\Windows`r`n"
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($null, 'powershell.exe $pwd.Path', "C:\Windows", $null)
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess without environment" = {
|
||||
$expected = "$($env:USERNAME)`r`n"
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($null, 'powershell.exe $env:TEST; $env:USERNAME', $null, $null)
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess with environment" = {
|
||||
$env_vars = @{
|
||||
TEST = "tesTing"
|
||||
TEST2 = "Testing 2"
|
||||
}
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($null, 'cmd.exe /c set', $null, $env_vars)
|
||||
("TEST=tesTing" -cin $actual.StandardOut.Split("`r`n")) | Assert-Equals -Expected $true
|
||||
("TEST2=Testing 2" -cin $actual.StandardOut.Split("`r`n")) | Assert-Equals -Expected $true
|
||||
("USERNAME=$($env:USERNAME)" -cnotin $actual.StandardOut.Split("`r`n")) | Assert-Equals -Expected $true
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess with string stdin" = {
|
||||
$expected = "input value`r`n`r`n"
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($null, 'powershell.exe [System.Console]::In.ReadToEnd()',
|
||||
$null, $null, "input value")
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess with string stdin and newline" = {
|
||||
$expected = "input value`r`n`r`n"
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($null, 'powershell.exe [System.Console]::In.ReadToEnd()',
|
||||
$null, $null, "input value`r`n")
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess with byte stdin" = {
|
||||
$expected = "input value`r`n"
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($null, 'powershell.exe [System.Console]::In.ReadToEnd()',
|
||||
$null, $null, [System.Text.Encoding]::UTF8.GetBytes("input value"))
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess with byte stdin and newline" = {
|
||||
$expected = "input value`r`n`r`n"
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($null, 'powershell.exe [System.Console]::In.ReadToEnd()',
|
||||
$null, $null, [System.Text.Encoding]::UTF8.GetBytes("input value`r`n"))
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
|
||||
"CreateProcess with lpApplicationName" = {
|
||||
$expected = "abc`r`n"
|
||||
$full_path = "$($env:SystemRoot)\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($full_path, "Write-Output 'abc'", $null, $null)
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
|
||||
$actual = [Ansible.Process.ProcessUtil]::CreateProcess($full_path, "powershell.exe Write-Output 'abc'", $null, $null)
|
||||
$actual.StandardOut | Assert-Equals -Expected $expected
|
||||
$actual.StandardError | Assert-Equals -Expected ""
|
||||
$actual.ExitCode | Assert-Equals -Expected 0
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($test_impl in $tests.GetEnumerator()) {
|
||||
$test = $test_impl.Key
|
||||
&$test_impl.Value
|
||||
}
|
||||
|
||||
$module.Result.data = "success"
|
||||
$module.ExitJson()
|
||||
|
|
@ -7,3 +7,40 @@
|
|||
assert:
|
||||
that:
|
||||
- ansible_basic_test.data == "success"
|
||||
|
||||
# Users by default don't have this right, temporarily enable it
|
||||
- name: ensure the Users group have the SeBatchLogonRight
|
||||
win_user_right:
|
||||
name: SeBatchLogonRight
|
||||
users:
|
||||
- Users
|
||||
action: add
|
||||
register: batch_user_add
|
||||
|
||||
- block:
|
||||
- name: test Ansible.Become.cs
|
||||
ansible_become_tests:
|
||||
register: ansible_become_tests
|
||||
|
||||
always:
|
||||
- name: remove SeBatchLogonRight from users if added in test
|
||||
win_user_right:
|
||||
name: SeBatchLogonRight
|
||||
users:
|
||||
- Users
|
||||
action: remove
|
||||
when: batch_user_add is changed
|
||||
|
||||
- name: assert test Ansible.Become.cs
|
||||
assert:
|
||||
that:
|
||||
- ansible_become_tests.data == "success"
|
||||
|
||||
- name: test Ansible.Process.cs
|
||||
ansible_process_tests:
|
||||
register: ansible_process_tests
|
||||
|
||||
- name: assert test Ansible.Process.cs
|
||||
assert:
|
||||
that:
|
||||
- ansible_process_tests.data == "success"
|
||||
|
|
|
@ -34,7 +34,7 @@ try {
|
|||
$actual = Run-Command -command "C:\fakepath\$exe_filename arg1"
|
||||
Fail-Json -obj $result -message "Test $test_name failed`nCommand should have thrown an exception"
|
||||
} catch {
|
||||
Assert-Equals -actual $_.Exception.Message -expected "Exception calling `"SearchPath`" with `"1`" argument(s): `"Could not locate the following executable C:\fakepath\$exe_filename`""
|
||||
Assert-Equals -actual $_.Exception.Message -expected "Exception calling `"SearchPath`" with `"1`" argument(s): `"Could not find file 'C:\fakepath\$exe_filename'.`""
|
||||
}
|
||||
|
||||
$test_name = "exe in current folder"
|
||||
|
|
|
@ -2,6 +2,7 @@ examples/scripts/ConfigureRemotingForAnsible.ps1 PSAvoidUsingCmdletAliases
|
|||
examples/scripts/upgrade_to_ps3.ps1 PSAvoidUsingWriteHost
|
||||
examples/scripts/upgrade_to_ps3.ps1 PSUseApprovedVerbs
|
||||
lib/ansible/module_utils/powershell/Ansible.ModuleUtils.ArgvParser.psm1 PSUseApprovedVerbs
|
||||
lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 PSProvideCommentHelp # need to agree on best format for comment location
|
||||
lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 PSUseApprovedVerbs
|
||||
lib/ansible/module_utils/powershell/Ansible.ModuleUtils.FileUtil.psm1 PSProvideCommentHelp
|
||||
lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 PSAvoidUsingWMICmdlet
|
||||
|
|
Loading…
Reference in a new issue