context: using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.IO;
  using System.Linq;
  using System.Management.Automation;
  using System.Management.Automation.Runspaces;
  using System.Reflection;
  using System.Runtime.InteropServices;
  using System.Security.AccessControl;
  using System.Security.Principal;
  #if CORECLR
  using Newtonsoft.Json;
  #else
  using System.Web.Script.Serialization;
  #endif
  
  // System.Diagnostics.EventLog.dll reference different versioned dlls that are
  // loaded in PSCore, ignore CS1702 so the code will ignore this warning
  //NoWarn -Name CS1702 -CLR Core
  
  //AssemblyReference -Name Newtonsoft.Json.dll -CLR Core
  //AssemblyReference -Name System.ComponentModel.Primitives.dll -CLR Core
  //AssemblyReference -Name System.Diagnostics.EventLog.dll -CLR Core
  //AssemblyReference -Name System.IO.FileSystem.AccessControl.dll -CLR Core
  //AssemblyReference -Name System.Security.Principal.Windows.dll -CLR Core
  //AssemblyReference -Name System.Security.AccessControl.dll -CLR Core
  
  //AssemblyReference -Name System.Web.Extensions.dll -CLR Framework
  
  namespace Ansible.Basic
  {
      public class AnsibleModule
      {
          public delegate void ExitHandler(int rc);
          public static ExitHandler Exit = new ExitHandler(ExitModule);
  
          public delegate void WriteLineHandler(string line);
          public static WriteLineHandler WriteLine = new WriteLineHandler(WriteLineModule);
  
          private static List<string> BOOLEANS_TRUE = new List<string>() { "y", "yes", "on", "1", "true", "t", "1.0" };
          private static List<string> BOOLEANS_FALSE = new List<string>() { "n", "no", "off", "0", "false", "f", "0.0" };
  
          private string remoteTmp = Path.GetTempPath();
          private string tmpdir = null;
          private HashSet<string> noLogValues = new HashSet<string>();
          private List<string> optionsContext = new List<string>();
          private List<string> warnings = new List<string>();
          private List<Dictionary<string, string>> deprecations = new List<Dictionary<string, string>>();
          private List<string> cleanupFiles = new List<string>();
  
          private Dictionary<string, string> passVars = new Dictionary<string, string>()
          {
              // null values means no mapping, not used in Ansible.Basic.AnsibleModule
              { "check_mode", "CheckMode" },
              { "debug", "DebugMode" },
              { "diff", "DiffMode" },
              { "keep_remote_files", "KeepRemoteFiles" },
              { "module_name", "ModuleName" },
              { "no_log", "NoLog" },
              { "remote_tmp", "remoteTmp" },
              { "selinux_special_fs", null },
              { "shell_executable", null },
              { "socket", null },
              { "syslog_facility", null },
              { "tmpdir", "tmpdir" },
              { "verbosity", "Verbosity" },
              { "version", "AnsibleVersion" },
          };
          private List<string> passBools = new List<string>() { "check_mode", "debug", "diff", "keep_remote_files", "no_log" };
          private List<string> passInts = new List<string>() { "verbosity" };
          private Dictionary<string, List<object>> specDefaults = new Dictionary<string, List<object>>()
          {
              // key - (default, type) - null is freeform
              { "apply_defaults", new List<object>() { false, typeof(bool) } },
              { "aliases", new List<object>() { typeof(List<string>), typeof(List<string>) } },
              { "choices", new List<object>() { typeof(List<object>), typeof(List<object>) } },
              { "default", new List<object>() { null, null } },
              { "elements", new List<object>() { null, null } },
              { "mutually_exclusive", new List<object>() { typeof(List<List<string>>), null } },
              { "no_log", new List<object>() { false, typeof(bool) } },
              { "options", new List<object>() { typeof(Hashtable), typeof(Hashtable) } },
              { "removed_in_version", new List<object>() { null, typeof(string) } },
              { "required", new List<object>() { false, typeof(bool) } },
              { "required_if", new List<object>() { typeof(List<List<object>>), null } },
              { "required_one_of", new List<object>() { typeof(List<List<string>>), null } },
              { "required_together", new List<object>() { typeof(List<List<string>>), null } },
              { "supports_check_mode", new List<object>() { false, typeof(bool) } },
              { "type", new List<object>() { "str", null } },
          };
          private Dictionary<string, Delegate> optionTypes = new Dictionary<string, Delegate>()
          {
              { "bool", new Func<object, bool>(ParseBool) },
              { "dict", new Func<object, Dictionary<string, object>>(ParseDict) },
              { "float", new Func<object, float>(ParseFloat) },
              { "int", new Func<object, int>(ParseInt) },
              { "json", new Func<object, string>(ParseJson) },
              { "list", new Func<object, List<object>>(ParseList) },
              { "path", new Func<object, string>(ParsePath) },
              { "raw", new Func<object, object>(ParseRaw) },
              { "sid", new Func<object, SecurityIdentifier>(ParseSid) },
              { "str", new Func<object, string>(ParseStr) },
          };
  
          public Dictionary<string, object> Diff = new Dictionary<string, object>();
          public IDictionary Params = null;
          public Dictionary<string, object> Result = new Dictionary<string, object>() { { "changed", false } };
  
          public bool CheckMode { get; private set; }
          public bool DebugMode { get; private set; }
          public bool DiffMode { get; private set; }
          public bool KeepRemoteFiles { get; private set; }
          public string ModuleName { get; private set; }
          public bool NoLog { get; private set; }
          public int Verbosity { get; private set; }
          public string AnsibleVersion { get; private set; }
  
          public string Tmpdir
          {
              get
              {
                  if (tmpdir == null)
                  {
                      SecurityIdentifier user = WindowsIdentity.GetCurrent().User;
                      DirectorySecurity dirSecurity = new DirectorySecurity();
                      dirSecurity.SetOwner(user);
                      dirSecurity.SetAccessRuleProtection(true, false);  // disable inheritance rules
                      FileSystemAccessRule ace = new FileSystemAccessRule(user, FileSystemRights.FullControl,
                          InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
                          PropagationFlags.None, AccessControlType.Allow);
                      dirSecurity.AddAccessRule(ace);
  
                      string baseDir = Path.GetFullPath(Environment.ExpandEnvironmentVariables(remoteTmp));
                      if (!Directory.Exists(baseDir))
                      {
                          string failedMsg = null;
                          try
                          {
  #if CORECLR
                              DirectoryInfo createdDir = Directory.CreateDirectory(baseDir);
                              FileSystemAclExtensions.SetAccessControl(createdDir, dirSecurity);
  #else
                              Directory.CreateDirectory(baseDir, dirSecurity);
  #endif
                          }
                          catch (Exception e)
                          {
                              failedMsg = String.Format("Failed to create base tmpdir '{0}': {1}", baseDir, e.Message);
                          }
  
                          if (failedMsg != null)
                          {
                              string envTmp = Path.GetTempPath();
                              Warn(String.Format("Unable to use '{0}' as temporary directory, falling back to system tmp '{1}': {2}", baseDir, envTmp, failedMsg));
                              baseDir = envTmp;
                          }
                          else
                          {
                              NTAccount currentUser = (NTAccount)user.Translate(typeof(NTAccount));
                              string warnMsg = String.Format("Module remote_tmp {0} did not exist and was created with FullControl to {1}, ", baseDir, currentUser.ToString());
                              warnMsg += "this may cause issues when running as another user. To avoid this, create the remote_tmp dir with the correct permissions manually";
                              Warn(warnMsg);
                          }
                      }
  
                      string dateTime = DateTime.Now.ToFileTime().ToString();
                      string dirName = String.Format("ansible-moduletmp-{0}-{1}", dateTime, new Random().Next(0, int.MaxValue));
                      string newTmpdir = Path.Combine(baseDir, dirName);
  #if CORECLR
                      DirectoryInfo tmpdirInfo = Directory.CreateDirectory(newTmpdir);
                      FileSystemAclExtensions.SetAccessControl(tmpdirInfo, dirSecurity);
  #else
                      Directory.CreateDirectory(newTmpdir, dirSecurity);
  #endif
                      tmpdir = newTmpdir;
  
                      if (!KeepRemoteFiles)
                          cleanupFiles.Add(tmpdir);
                  }
                  return tmpdir;
              }
          }
  
          public AnsibleModule(string[] args, IDictionary argumentSpec)
          {
              // NoLog is not set yet, we cannot rely on FailJson to sanitize the output
              // Do the minimum amount to get this running before we actually parse the params
              Dictionary<string, string> aliases = new Dictionary<string, string>();
              try
              {
                  ValidateArgumentSpec(argumentSpec);
                  Params = GetParams(args);
                  aliases = GetAliases(argumentSpec, Params);
                  SetNoLogValues(argumentSpec, Params);
              }
              catch (Exception e)
              {
                  Dictionary<string, object> result = new Dictionary<string, object>
                  {
                      { "failed", true },
                      { "msg", String.Format("internal error: {0}", e.Message) },
                      { "exception", e.ToString() }
                  };
                  WriteLine(ToJson(result));
                  Exit(1);
              }
  
              // Initialise public properties to the defaults before we parse the actual inputs
              CheckMode = false;
              DebugMode = false;
              DiffMode = false;
              KeepRemoteFiles = false;
              ModuleName = "undefined win module";
              NoLog = (bool)argumentSpec["no_log"];
              Verbosity = 0;
              AppDomain.CurrentDomain.ProcessExit += CleanupFiles;
  
              List<string> legalInputs = passVars.Keys.Select(v => "_ansible_" + v).ToList();
              legalInputs.AddRange(((IDictionary)argumentSpec["options"]).Keys.Cast<string>().ToList());
              legalInputs.AddRange(aliases.Keys.Cast<string>().ToList());
              CheckArguments(argumentSpec, Params, legalInputs);
  
              // Set a Ansible friendly invocation value in the result object
              Dictionary<string, object> invocation = new Dictionary<string, object>() { { "module_args", Params } };
              Result["invocation"] = RemoveNoLogValues(invocation, noLogValues);
  
              if (!NoLog)
                  LogEvent(String.Format("Invoked with:\r\n  {0}", FormatLogData(Params, 2)), sanitise: false);
          }
  
          public static AnsibleModule Create(string[] args, IDictionary argumentSpec)
          {
              return new AnsibleModule(args, argumentSpec);
          }
  
          public void Debug(string message)
          {
              if (DebugMode)
                  LogEvent(String.Format("[DEBUG] {0}", message));
          }
  
          public void Deprecate(string message, string version)
          {
              deprecations.Add(new Dictionary<string, string>() { { "msg", message }, { "version", version } });
              LogEvent(String.Format("[DEPRECATION WARNING] {0} {1}", message, version));
          }
  
          public void ExitJson()
          {
              WriteLine(GetFormattedResults(Result));
              CleanupFiles(null, null);
              Exit(0);
          }
  
          public void FailJson(string message) { FailJson(message, null, null); }
          public void FailJson(string message, ErrorRecord psErrorRecord) { FailJson(message, psErrorRecord, null); }
          public void FailJson(string message, Exception exception) { FailJson(message, null, exception); }
          private void FailJson(string message, ErrorRecord psErrorRecord, Exception exception)
          {
              Result["failed"] = true;
              Result["msg"] = RemoveNoLogValues(message, noLogValues);
  
  
              if (!Result.ContainsKey("exception") && (Verbosity > 2 || DebugMode))
              {
                  if (psErrorRecord != null)
                  {
                      string traceback = String.Format("{0}\r\n{1}", psErrorRecord.ToString(), psErrorRecord.InvocationInfo.PositionMessage);
                      traceback += String.Format("\r\n    + CategoryInfo          : {0}", psErrorRecord.CategoryInfo.ToString());
                      traceback += String.Format("\r\n    + FullyQualifiedErrorId : {0}", psErrorRecord.FullyQualifiedErrorId.ToString());
                      traceback += String.Format("\r\n\r\nScriptStackTrace:\r\n{0}", psErrorRecord.ScriptStackTrace);
                      Result["exception"] = traceback;
                  }
                  else if (exception != null)
                      Result["exception"] = exception.ToString();
              }
  
              WriteLine(GetFormattedResults(Result));
              CleanupFiles(null, null);
              Exit(1);
          }
  
          public void LogEvent(string message, EventLogEntryType logEntryType = EventLogEntryType.Information, bool sanitise = true)
          {
              if (NoLog)
                  return;
  
              string logSource = "Ansible";
              bool logSourceExists = false;
              try
              {
                  logSourceExists = EventLog.SourceExists(logSource);
              }
              catch (System.Security.SecurityException) { }  // non admin users may not have permission
  
              if (!logSourceExists)
              {
                  try
                  {
                      EventLog.CreateEventSource(logSource, "Application");
                  }
                  catch (System.Security.SecurityException)
                  {
                      Warn(String.Format("Access error when creating EventLog source {0}, logging to the Application source instead", logSource));
                      logSource = "Application";
                  }
              }
              if (sanitise)
                  message = (string)RemoveNoLogValues(message, noLogValues);
              message = String.Format("{0} - {1}", ModuleName, message);
  
              using (EventLog eventLog = new EventLog("Application"))
              {
                  eventLog.Source = logSource;
                  eventLog.WriteEntry(message, logEntryType, 0);
              }
          }
  
          public void Warn(string message)
          {
              warnings.Add(message);
              LogEvent(String.Format("[WARNING] {0}", message), EventLogEntryType.Warning);
          }
  
          public static Dictionary<string, object> FromJson(string json) { return FromJson<Dictionary<string, object>>(json); }
          public static T FromJson<T>(string json)
          {
  #if CORECLR
              return JsonConvert.DeserializeObject<T>(json);
  #else
              JavaScriptSerializer jss = new JavaScriptSerializer();
              jss.MaxJsonLength = int.MaxValue;
              jss.RecursionLimit = int.MaxValue;
              return jss.Deserialize<T>(json);
  #endif
          }
  
          public static string ToJson(object obj)
          {
  #if CORECLR
              return JsonConvert.SerializeObject(obj);
  #else
              JavaScriptSerializer jss = new JavaScriptSerializer();
              jss.MaxJsonLength = int.MaxValue;
              jss.RecursionLimit = int.MaxValue;
              return jss.Serialize(obj);
  #endif
          }
  
          public static IDictionary GetParams(string[] args)
          {
              if (args.Length > 0)
              {
                  string inputJson = File.ReadAllText(args[0]);
                  Dictionary<string, object> rawParams = FromJson(inputJson);
                  if (!rawParams.ContainsKey("ANSIBLE_MODULE_ARGS"))
                      throw new ArgumentException("Module was unable to get ANSIBLE_MODULE_ARGS value from the argument path json");
                  return (IDictionary)rawParams["ANSIBLE_MODULE_ARGS"];
              }
              else
              {
                  // $complex_args is already a Hashtable, no need to waste time converting to a dictionary
                  PSObject rawArgs = ScriptBlock.Create("$complex_args").Invoke()[0];
                  return rawArgs.BaseObject as Hashtable;
              }
          }
  
          public static bool ParseBool(object value)
          {
              if (value.GetType() == typeof(bool))
                  return (bool)value;
  
              List<string> booleans = new List<string>();
              booleans.AddRange(BOOLEANS_TRUE);
              booleans.AddRange(BOOLEANS_FALSE);
  
              string stringValue = ParseStr(value).ToLowerInvariant().Trim();
              if (BOOLEANS_TRUE.Contains(stringValue))
                  return true;
              else if (BOOLEANS_FALSE.Contains(stringValue))
                  return false;
  
              string msg = String.Format("The value '{0}' is not a valid boolean. Valid booleans include: {1}",
                  stringValue, String.Join(", ", booleans));
              throw new ArgumentException(msg);
          }
  
          public static Dictionary<string, object> ParseDict(object value)
          {
              Type valueType = value.GetType();
              if (valueType == typeof(Dictionary<string, object>))
                  return (Dictionary<string, object>)value;
              else if (value is IDictionary)
                  return ((IDictionary)value).Cast<DictionaryEntry>().ToDictionary(kvp => (string)kvp.Key, kvp => kvp.Value);
              else if (valueType == typeof(string))
              {
                  string stringValue = (string)value;
                  if (stringValue.StartsWith("{") && stringValue.EndsWith("}"))
                      return FromJson<Dictionary<string, object>>((string)value);
                  else if (stringValue.IndexOfAny(new char[1] { '=' }) != -1)
                  {
                      List<string> fields = new List<string>();
                      List<char> fieldBuffer = new List<char>();
                      char? inQuote = null;
                      bool inEscape = false;
                      string field;
  
                      foreach (char c in stringValue.ToCharArray())
                      {
                          if (inEscape)
                          {
                              fieldBuffer.Add(c);
                              inEscape = false;
                          }
                          else if (c == '\\')
                              inEscape = true;
                          else if (inQuote == null && (c == '\'' || c == '"'))
                              inQuote = c;
                          else if (inQuote != null && c == inQuote)
                              inQuote = null;
                          else if (inQuote == null && (c == ',' || c == ' '))
                          {
                              field = String.Join("", fieldBuffer);
                              if (field != "")
                                  fields.Add(field);
                              fieldBuffer = new List<char>();
                          }
                          else
                              fieldBuffer.Add(c);
                      }
  
                      field = String.Join("", fieldBuffer);
                      if (field != "")
                          fields.Add(field);
  
                      return fields.Distinct().Select(i => i.Split(new[] { '=' }, 2)).ToDictionary(i => i[0], i => i.Length > 1 ? (object)i[1] : null);
                  }
                  else
                      throw new ArgumentException("string cannot be converted to a dict, must either be a JSON string or in the key=value form");
              }
  
              throw new ArgumentException(String.Format("{0} cannot be converted to a dict", valueType.FullName));
          }
  
          public static float ParseFloat(object value)
          {
              if (value.GetType() == typeof(float))
                  return (float)value;
  
              string valueStr = ParseStr(value);
              return float.Parse(valueStr);
          }
  
          public static int ParseInt(object value)
          {
              Type valueType = value.GetType();
              if (valueType == typeof(int))
                  return (int)value;
              else
                  return Int32.Parse(ParseStr(value));
          }
  
          public static string ParseJson(object value)
          {
              // mostly used to ensure a dict is a json string as it may
              // have been converted on the controller side
              Type valueType = value.GetType();
              if (value is IDictionary)
                  return ToJson(value);
              else if (valueType == typeof(string))
                  return (string)value;
              else
                  throw new ArgumentException(String.Format("{0} cannot be converted to json", valueType.FullName));
          }
  
          public static List<object> ParseList(object value)
          {
              if (value == null)
                  return null;
  
              Type valueType = value.GetType();
              if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(List<>))
                  return (List<object>)value;
              else if (valueType == typeof(ArrayList))
                  return ((ArrayList)value).Cast<object>().ToList();
              else if (valueType.IsArray)
                  return ((object[])value).ToList();
              else if (valueType == typeof(string))
                  return ((string)value).Split(',').Select(s => s.Trim()).ToList<object>();
              else if (valueType == typeof(int))
                  return new List<object>() { value };
              else
                  throw new ArgumentException(String.Format("{0} cannot be converted to a list", valueType.FullName));
          }
  
          public static string ParsePath(object value)
          {
              string stringValue = ParseStr(value);
  
              // do not validate, expand the env vars if it starts with \\?\ as
              // it is a special path designed for the NT kernel to interpret
              if (stringValue.StartsWith(@"\\?\"))
                  return stringValue;
  
              stringValue = Environment.ExpandEnvironmentVariables(stringValue);
              if (stringValue.IndexOfAny(Path.GetInvalidPathChars()) != -1)
                  throw new ArgumentException("string value contains invalid path characters, cannot convert to path");
  
              // will fire an exception if it contains any invalid chars
              Path.GetFullPath(stringValue);
              return stringValue;
          }
  
          public static object ParseRaw(object value) { return value; }
  
          public static SecurityIdentifier ParseSid(object value)
          {
              string stringValue = ParseStr(value);
  
              try
              {
                  return new SecurityIdentifier(stringValue);
              }
              catch (ArgumentException) { }  // ignore failures string may not have been a SID
  
              NTAccount account = new NTAccount(stringValue);
              return (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
          }
  
          public static string ParseStr(object value) { return value.ToString(); }
  
          private void ValidateArgumentSpec(IDictionary argumentSpec)
          {
              Dictionary<string, object> changedValues = new Dictionary<string, object>();
              foreach (DictionaryEntry entry in argumentSpec)
              {
                  string key = (string)entry.Key;
  
                  // validate the key is a valid argument spec key
                  if (!specDefaults.ContainsKey(key))
                  {
                      string msg = String.Format("argument spec entry contains an invalid key '{0}', valid keys: {1}",
                          key, String.Join(", ", specDefaults.Keys));
                      if (optionsContext.Count > 0)
                          msg += String.Format(" - found in {0}.", String.Join(" -> ", optionsContext));
                      throw new ArgumentException(msg);
                  }
  
                  // ensure the value is casted to the type we expect
                  Type optionType = null;
                  if (entry.Value != null)
                      optionType = (Type)specDefaults[key][1];
                  if (optionType != null)
                  {
                      Type actualType = entry.Value.GetType();
                      bool invalid = false;
                      if (optionType.IsGenericType && optionType.GetGenericTypeDefinition() == typeof(List<>))
                      {
                          // verify the actual type is not just a single value of the list type
                          Type entryType = optionType.GetGenericArguments()[0];
  
                          bool isArray = actualType.IsArray && (actualType.GetElementType() == entryType || actualType.GetElementType() == typeof(object));
                          if (actualType == entryType || isArray)
                          {
                              object[] rawArray;
                              if (isArray)
                                  rawArray = (object[])entry.Value;
                              else
                                  rawArray = new object[1] { entry.Value };
  
                              MethodInfo castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(entryType);
                              MethodInfo toListMethod = typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(entryType);
  
                              var enumerable = castMethod.Invoke(null, new object[1] { rawArray });
                              var newList = toListMethod.Invoke(null, new object[1] { enumerable });
                              changedValues.Add(key, newList);
                          }
                          else if (actualType != optionType && !(actualType == typeof(List<object>)))
                              invalid = true;
                      }
                      else
                          invalid = actualType != optionType;
  
                      if (invalid)
                      {
                          string msg = String.Format("argument spec for '{0}' did not match expected type {1}: actual type {2}",
                              key, optionType.FullName, actualType.FullName);
                          if (optionsContext.Count > 0)
                              msg += String.Format(" - found in {0}.", String.Join(" -> ", optionsContext));
                          throw new ArgumentException(msg);
                      }
                  }
  
                  // recursively validate the spec
                  if (key == "options" && entry.Value != null)
                  {
                      IDictionary optionsSpec = (IDictionary)entry.Value;
                      foreach (DictionaryEntry optionEntry in optionsSpec)
                      {
                          optionsContext.Add((string)optionEntry.Key);
                          IDictionary optionMeta = (IDictionary)optionEntry.Value;
                          ValidateArgumentSpec(optionMeta);
                          optionsContext.RemoveAt(optionsContext.Count - 1);
                      }
                  }
  
                  // validate the type and elements key type values are known types
                  if (key == "type" || key == "elements" && entry.Value != null)
                  {
                      Type valueType = entry.Value.GetType();
                      if (valueType == typeof(string))
                      {
                          string typeValue = (string)entry.Value;
                          if (!optionTypes.ContainsKey(typeValue))
                          {
                              string msg = String.Format("{0} '{1}' is unsupported", key, typeValue);
                              if (optionsContext.Count > 0)
                                  msg += String.Format(" - found in {0}", String.Join(" -> ", optionsContext));
                              msg += String.Format(". Valid types are: {0}", String.Join(", ", optionTypes.Keys));
                              throw new ArgumentException(msg);
                          }
                      }
                      else if (!(entry.Value is Delegate))
                      {
                          string msg = String.Format("{0} must either be a string or delegate, was: {1}", key, valueType.FullName);
                          if (optionsContext.Count > 0)
                              msg += String.Format(" - found in {0}", String.Join(" -> ", optionsContext));
                          throw new ArgumentException(msg);
                      }
                  }
              }
  
              // Outside of the spec iterator, change the values that were casted above
              foreach (KeyValuePair<string, object> changedValue in changedValues)
                  argumentSpec[changedValue.Key] = changedValue.Value;
  
              // Now make sure all the metadata keys are set to their defaults
              foreach (KeyValuePair<string, List<object>> metadataEntry in specDefaults)
              {
                  List<object> defaults = metadataEntry.Value;
                  object defaultValue = defaults[0];
                  if (defaultValue != null && defaultValue.GetType() == typeof(Type).GetType())
                      defaultValue = Activator.CreateInstance((Type)defaultValue);
  
                  if (!argumentSpec.Contains(metadataEntry.Key))
                      argumentSpec[metadataEntry.Key] = defaultValue;
              }
          }
  
          private Dictionary<string, string> GetAliases(IDictionary argumentSpec, IDictionary parameters)
          {
              Dictionary<string, string> aliasResults = new Dictionary<string, string>();
  
              foreach (DictionaryEntry entry in (IDictionary)argumentSpec["options"])
              {
                  string k = (string)entry.Key;
                  Hashtable v = (Hashtable)entry.Value;
  
                  List<string> aliases = (List<string>)v["aliases"];
                  object defaultValue = v["default"];
                  bool required = (bool)v["required"];
  
                  if (defaultValue != null && required)
                      throw new ArgumentException(String.Format("required and default are mutually exclusive for {0}", k));
  
                  foreach (string alias in aliases)
                  {
                      aliasResults.Add(alias, k);
                      if (parameters.Contains(alias))
                          parameters[k] = parameters[alias];
                  }
              }
  
              return aliasResults;
          }
  
          private void SetNoLogValues(IDictionary argumentSpec, IDictionary parameters)
          {
              foreach (DictionaryEntry entry in (IDictionary)argumentSpec["options"])
              {
                  string k = (string)entry.Key;
                  Hashtable v = (Hashtable)entry.Value;
  
                  if ((bool)v["no_log"])
                  {
                      object noLogObject = parameters.Contains(k) ? parameters[k] : null;
                      if (noLogObject != null)
                          noLogValues.Add(noLogObject.ToString());
                  }
  
                  object removedInVersion = v["removed_in_version"];
                  if (removedInVersion != null && parameters.Contains(k))
                      Deprecate(String.Format("Param '{0}' is deprecated. See the module docs for more information", k), removedInVersion.ToString());
              }
          }
  
          private void CheckArguments(IDictionary spec, IDictionary param, List<string> legalInputs)
          {
              // initially parse the params and check for unsupported ones and set internal vars
              CheckUnsupportedArguments(param, legalInputs);
  
              // Only run this check if we are at the root argument (optionsContext.Count == 0)
              if (CheckMode && !(bool)spec["supports_check_mode"] && optionsContext.Count == 0)
              {
                  Result["skipped"] = true;
                  Result["msg"] = String.Format("remote module ({0}) does not support check mode", ModuleName);
                  ExitJson();
              }
              IDictionary optionSpec = (IDictionary)spec["options"];
  
              CheckMutuallyExclusive(param, (IList)spec["mutually_exclusive"]);
              CheckRequiredArguments(optionSpec, param);
  
              // set the parameter types based on the type spec value
              foreach (DictionaryEntry entry in optionSpec)
              {
                  string k = (string)entry.Key;
                  Hashtable v = (Hashtable)entry.Value;
  
                  object value = param.Contains(k) ? param[k] : null;
                  if (value != null)
                  {
                      // convert the current value to the wanted type
                      Delegate typeConverter;
                      string type;
                      if (v["type"].GetType() == typeof(string))
                      {
                          type = (string)v["type"];
                          typeConverter = optionTypes[type];
                      }
                      else
                      {
                          type = "delegate";
                          typeConverter = (Delegate)v["type"];
                      }
  
                      try
                      {
                          value = typeConverter.DynamicInvoke(value);
                          param[k] = value;
                      }
                      catch (Exception e)
                      {
                          string msg = String.Format("argument for {0} is of type {1} and we were unable to convert to {2}: {3}",
                              k, value.GetType(), type, e.InnerException.Message);
                          if (optionsContext.Count > 0)
                              msg += String.Format(" found in {0}", String.Join(" -> ", optionsContext));
                          FailJson(msg);
                      }
  
                      // ensure it matches the choices if there are choices set
                      List<object> choices = (List<object>)v["choices"];
                      if (choices.Count > 0)
                      {
                          // allow one or more when type="list" param with choices
                          if (type == "list")
                          {
                              var diffList = ((List<object>)value).Except(choices).ToList();
  
gt:                             if (diffList.Count > 0)