// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using Dbg = System.Management.Automation.Diagnostics;
using MethodCacheEntry = System.Management.Automation.DotNetAdapter.MethodCacheEntry;
#if !UNIX
using System.DirectoryServices;
using System.Management;
#endif
#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings
#pragma warning disable 56500
namespace System.Management.Automation
{
#region public type converters
///
/// Defines a base class implemented when you need to customize the type conversion for a target class.
///
///
/// There are two ways of associating the PSTypeConverter with its target class:
/// - Through the type configuration file.
/// - By applying a TypeConverterAttribute to the target class.
///
/// Unlike System.ComponentModel.TypeConverter, PSTypeConverter can be applied to a family of types (like all types derived from System.Enum).
/// PSTypeConverter has two main differences from TypeConverter:
/// - It can be applied to a family of types and not only the one type as in TypeConverter. In order to do that
/// ConvertFrom and CanConvertFrom receive destinationType to know to which type specifically we are converting sourceValue.
/// - ConvertTo and ConvertFrom receive formatProvider and ignoreCase.
/// Other differences to System.ComponentModel.TypeConverter:
/// - There is no ITypeDescriptorContext.
/// - This class is abstract
///
public abstract class PSTypeConverter
{
private static object GetSourceValueAsObject(PSObject sourceValue)
{
if (sourceValue == null)
{
return null;
}
if (sourceValue.BaseObject is PSCustomObject)
{
return sourceValue;
}
else
{
return PSObject.Base(sourceValue);
}
}
///
/// Determines if the converter can convert the parameter to the parameter.
///
/// Value supposedly *not* of the types supported by this converted to be converted to the parameter.
/// One of the types supported by this converter to which the parameter should be converted.
/// True if the converter can convert the parameter to the parameter, otherwise false.
public abstract bool CanConvertFrom(object sourceValue, Type destinationType);
///
/// Determines if the converter can convert the parameter to the parameter.
///
/// Value supposedly *not* of the types supported by this converted to be converted to the parameter.
/// One of the types supported by this converter to which the parameter should be converted.
/// True if the converter can convert the parameter to the parameter, otherwise false.
public virtual bool CanConvertFrom(PSObject sourceValue, Type destinationType)
{
return this.CanConvertFrom(GetSourceValueAsObject(sourceValue), destinationType);
}
///
/// Converts the parameter to the parameter using formatProvider and ignoreCase.
///
/// Value supposedly *not* of the types supported by this converted to be converted to the parameter.
/// One of the types supported by this converter to which the parameter should be converted to.
/// The format provider to use like in IFormattable's ToString.
/// True if case should be ignored.
/// The parameter converted to the parameter using formatProvider and ignoreCase.
/// If no conversion was possible.
public abstract object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase);
///
/// Converts the parameter to the parameter using formatProvider and ignoreCase.
///
/// Value supposedly *not* of the types supported by this converted to be converted to the parameter.
/// One of the types supported by this converter to which the parameter should be converted to.
/// The format provider to use like in IFormattable's ToString.
/// True if case should be ignored.
/// The parameter converted to the parameter using formatProvider and ignoreCase.
/// If no conversion was possible.
public virtual object ConvertFrom(PSObject sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase)
{
return this.ConvertFrom(GetSourceValueAsObject(sourceValue), destinationType, formatProvider, ignoreCase);
}
///
/// Returns true if the converter can convert the parameter to the parameter.
///
/// Value supposedly from one of the types supported by this converter to be converted to the parameter.
/// Type to convert the parameter, supposedly not one of the types supported by the converter.
/// True if the converter can convert the parameter to the parameter, otherwise false.
public abstract bool CanConvertTo(object sourceValue, Type destinationType);
///
/// Returns true if the converter can convert the parameter to the parameter.
///
/// Value supposedly from one of the types supported by this converter to be converted to the parameter.
/// Type to convert the parameter, supposedly not one of the types supported by the converter.
/// True if the converter can convert the parameter to the parameter, otherwise false.
public virtual bool CanConvertTo(PSObject sourceValue, Type destinationType)
{
return this.CanConvertTo(GetSourceValueAsObject(sourceValue), destinationType);
}
///
/// Converts the parameter to the parameter using formatProvider and ignoreCase.
///
/// Value supposedly from one of the types supported by this converter to be converted to the parameter.
/// Type to convert the parameter, supposedly not one of the types supported by the converter.
/// The format provider to use like in IFormattable's ToString.
/// True if case should be ignored.
/// SourceValue converted to the parameter using formatProvider and ignoreCase.
/// If no conversion was possible.
public abstract object ConvertTo(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase);
///
/// Converts the parameter to the parameter using formatProvider and ignoreCase.
///
/// Value supposedly from one of the types supported by this converter to be converted to the parameter.
/// Type to convert the parameter, supposedly not one of the types supported by the converter.
/// The format provider to use like in IFormattable's ToString.
/// True if case should be ignored.
/// SourceValue converted to the parameter using formatProvider and ignoreCase.
/// If no conversion was possible.
public virtual object ConvertTo(PSObject sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase)
{
return this.ConvertTo(GetSourceValueAsObject(sourceValue), destinationType, formatProvider, ignoreCase);
}
}
///
/// Enables a type that only has conversion from string to be converted from all other
/// types through string.
///
///
/// It is permitted to subclass
/// but there is no established scenario for doing this, nor has it been tested.
///
public class ConvertThroughString : PSTypeConverter
{
///
/// This will return false only if sourceValue is string.
///
/// Value to convert from.
/// Ignored.
/// False only if sourceValue is string.
public override bool CanConvertFrom(object sourceValue, Type destinationType)
{
// This if avoids infinite recursion.
if (sourceValue is string)
{
return false;
}
return true;
}
///
/// Converts to destinationType by first converting sourceValue to string
/// and then converting the result to destinationType.
///
/// The value to convert from.
/// The type this converter is associated with.
/// The IFormatProvider to use.
/// True if case should be ignored.
/// SourceValue converted to destinationType.
/// When no conversion was possible.
public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase)
{
string sourceAsString = (string)LanguagePrimitives.ConvertTo(sourceValue, typeof(string), formatProvider);
return LanguagePrimitives.ConvertTo(sourceAsString, destinationType, formatProvider);
}
///
/// Returns false, since this converter is not designed to be used to
/// convert from the type associated with the converted to other types.
///
/// The value to convert from.
/// The value to convert from.
/// False.
public override bool CanConvertTo(object sourceValue, Type destinationType)
{
return false;
}
///
/// Throws NotSupportedException, since this converter is not designed to be used to
/// convert from the type associated with the converted to other types.
///
/// The value to convert from.
/// The value to convert from.
/// The IFormatProvider to use.
/// True if case should be ignored.
/// This method does not return a value.
/// NotSupportedException is always thrown.
public override object ConvertTo(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase)
{
throw PSTraceSource.NewNotSupportedException();
}
}
#endregion public type converters
///
/// The ranking of versions for comparison purposes (used in overload resolution.)
/// A larger value means the conversion is better.
///
/// Note that the lower nibble is all ones for named conversion ranks. This allows for
/// conversions with rankings in between the named values. For example, int=>string[]
/// is value dependent, if the conversion from int=>string succeeds, then an array is
/// created, otherwise we try some other conversion. The int=>string[] conversion should
/// be worse than int=>string, but it is probably better than many other conversions, so
/// we want it to be only slightly worse than int=>string.
///
///
/// ValueDependent is a flag, but we don't mark the enum as flags because it really isn't
/// a flags enum.
/// To make debugging easier, the "in between" conversions are all named, though there are
/// no references in the code. They all use the suffix S2A which means "scalar to array".
///
internal enum ConversionRank
{
None = 0x0000,
UnrelatedArraysS2A = 0x0007,
UnrelatedArrays = 0x000F,
ToStringS2A = 0x0017,
ToString = 0x001F,
CustomS2A = 0x0027,
Custom = 0x002F,
IConvertibleS2A = 0x0037,
IConvertible = 0x003F,
ImplicitCastS2A = 0x0047,
ImplicitCast = 0x004F,
ExplicitCastS2A = 0x0057,
ExplicitCast = 0x005F,
ConstructorS2A = 0x0067,
Constructor = 0x006F,
Create = 0x0073,
ParseS2A = 0x0077,
Parse = 0x007F,
PSObjectS2A = 0x0087,
PSObject = 0x008F,
LanguageS2A = 0x0097,
Language = 0x009F,
NullToValue = 0x00AF,
NullToRef = 0x00BF,
NumericExplicitS2A = 0x00C7,
NumericExplicit = 0x00CF,
NumericExplicit1S2A = 0x00D7,
NumericExplicit1 = 0x00DF,
NumericStringS2A = 0x00E7,
NumericString = 0x00EF,
NumericImplicitS2A = 0x00F7,
NumericImplicit = 0x00FF,
AssignableS2A = 0x0107,
Assignable = 0x010F,
IdentityS2A = 0x0117,
StringToCharArray = 0x011A,
Identity = 0x011F,
ValueDependent = 0xFFF7,
}
///
/// Defines language support methods.
///
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Refactoring LanguagePrimitives takes lot of dev/test effort. Since V1 code is already shipped, we tend to exclude this message.")]
public static class LanguagePrimitives
{
[TraceSource("ETS", "Extended Type System")]
private static PSTraceSource s_tracer = PSTraceSource.GetTracer("ETS", "Extended Type System");
internal delegate void MemberNotFoundError(PSObject pso, DictionaryEntry property, Type resultType);
internal delegate void MemberSetValueError(SetValueException e);
internal const string OrderedAttribute = "ordered";
internal const string DoublePrecision = "G15";
internal const string SinglePrecision = "G7";
internal static void CreateMemberNotFoundError(PSObject pso, DictionaryEntry property, Type resultType)
{
string availableProperties = GetAvailableProperties(pso);
string message = StringUtil.Format(ExtendedTypeSystem.PropertyNotFound, property.Key.ToString(), resultType.FullName, availableProperties);
typeConversion.WriteLine("Issuing an error message about not being able to create an object from hashtable.");
throw new InvalidOperationException(message);
}
internal static void CreateMemberSetValueError(SetValueException e)
{
typeConversion.WriteLine("Issuing an error message about not being able to set the properties for an object.");
throw e;
}
static LanguagePrimitives()
{
RebuildConversionCache();
InitializeGetEnumerableCache();
#if !CORECLR // AppDomain Not In CoreCLR
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolveHelper;
#endif
}
internal static void UpdateTypeConvertFromTypeTable(string typeName)
{
lock (s_converterCache)
{
var toRemove = s_converterCache.Keys.Where(
conv => string.Equals(conv.to.FullName, typeName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(conv.from.FullName, typeName, StringComparison.OrdinalIgnoreCase)).ToArray();
foreach (var k in toRemove)
{
s_converterCache.Remove(k);
}
// Note we do not clear possibleTypeConverter even when removing.
//
// The conversion cache and the possibleTypeConverter cache are process wide, but
// the type table used for any specific conversion is runspace specific.
s_possibleTypeConverter[typeName] = true;
}
}
#region GetEnumerable/GetEnumerator
///
/// This is a wrapper class that allows us to use the generic IEnumerable
/// implementation of an object when we can't use it's non-generic
/// implementation.
///
private class EnumerableTWrapper : IEnumerable
{
private object _enumerable;
private Type _enumerableType;
private DynamicMethod _getEnumerator;
internal EnumerableTWrapper(object enumerable, Type enumerableType)
{
_enumerable = enumerable;
_enumerableType = enumerableType;
CreateGetEnumerator();
}
private void CreateGetEnumerator()
{
_getEnumerator = new DynamicMethod("GetEnumerator", typeof(object),
new Type[] { typeof(object) }, typeof(LanguagePrimitives).Module, true);
ILGenerator emitter = _getEnumerator.GetILGenerator();
emitter.Emit(OpCodes.Ldarg_0);
emitter.Emit(OpCodes.Castclass, _enumerableType);
MethodInfo methodInfo = _enumerableType.GetMethod("GetEnumerator", new Type[] { });
emitter.Emit(OpCodes.Callvirt, methodInfo);
emitter.Emit(OpCodes.Ret);
}
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
return (IEnumerator)_getEnumerator.Invoke(null, new object[] { _enumerable });
}
#endregion
}
private static IEnumerable GetEnumerableFromIEnumerableT(object obj)
{
foreach (Type i in obj.GetType().GetInterfaces())
{
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return new EnumerableTWrapper(obj, i);
}
}
return null;
}
private delegate IEnumerable GetEnumerableDelegate(object obj);
private static Dictionary s_getEnumerableCache = new Dictionary(32);
private static GetEnumerableDelegate GetOrCalculateEnumerable(Type type)
{
GetEnumerableDelegate getEnumerable = null;
lock (s_getEnumerableCache)
{
if (!s_getEnumerableCache.TryGetValue(type, out getEnumerable))
{
getEnumerable = CalculateGetEnumerable(type);
s_getEnumerableCache.Add(type, getEnumerable);
}
}
return getEnumerable;
}
private static void InitializeGetEnumerableCache()
{
lock (s_getEnumerableCache)
{
// PowerShell doesn't treat strings as enumerables so just return null.
// we also want to return null on common numeric types very quickly
s_getEnumerableCache.Clear();
s_getEnumerableCache.Add(typeof(string), LanguagePrimitives.ReturnNullEnumerable);
s_getEnumerableCache.Add(typeof(int), LanguagePrimitives.ReturnNullEnumerable);
s_getEnumerableCache.Add(typeof(double), LanguagePrimitives.ReturnNullEnumerable);
}
}
internal static bool IsTypeEnumerable(Type type)
{
if (type == null) { return false; }
GetEnumerableDelegate getEnumerable = GetOrCalculateEnumerable(type);
return (getEnumerable != LanguagePrimitives.ReturnNullEnumerable);
}
///
/// Returns True if the language considers obj to be IEnumerable.
///
///
/// IEnumerable or IEnumerable-like object
///
[SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "Since V1 code is already shipped, excluding this message.")]
public static bool IsObjectEnumerable(object obj)
{
return IsTypeEnumerable(PSObject.Base(obj)?.GetType());
}
///
/// Retrieves the IEnumerable of obj or null if the language does not consider obj to be IEnumerable.
///
///
/// IEnumerable or IEnumerable-like object
///
[SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "Since V1 code is already shipped, excluding this message.")]
public static IEnumerable GetEnumerable(object obj)
{
obj = PSObject.Base(obj);
if (obj == null) { return null; }
GetEnumerableDelegate getEnumerable = GetOrCalculateEnumerable(obj.GetType());
return getEnumerable(obj);
}
private static IEnumerable ReturnNullEnumerable(object obj)
{
return null;
}
private static IEnumerable DataTableEnumerable(object obj)
{
return (((DataTable)obj).Rows);
}
private static IEnumerable TypicalEnumerable(object obj)
{
IEnumerable e = (IEnumerable)obj;
try
{
// Some IEnumerable implementations just return null. Others
// raise an exception. Either of these may have a perfectly
// good generic implementation, so we'll try those if the
// non-generic is no good.
if (e.GetEnumerator() == null)
{
return GetEnumerableFromIEnumerableT(obj);
}
return e;
}
catch (Exception innerException)
{
e = GetEnumerableFromIEnumerableT(obj);
if (e != null)
{
return e;
}
throw new ExtendedTypeSystemException(
"ExceptionInGetEnumerator",
innerException,
ExtendedTypeSystem.EnumerationException,
innerException.Message);
}
}
private static GetEnumerableDelegate CalculateGetEnumerable(Type objectType)
{
if (typeof(DataTable).IsAssignableFrom(objectType))
{
return LanguagePrimitives.DataTableEnumerable;
}
// Don't treat IDictionary or XmlNode as enumerable...
if (typeof(IEnumerable).IsAssignableFrom(objectType)
&& !typeof(IDictionary).IsAssignableFrom(objectType)
&& !typeof(XmlNode).IsAssignableFrom(objectType))
{
return LanguagePrimitives.TypicalEnumerable;
}
return LanguagePrimitives.ReturnNullEnumerable;
}
private static readonly CallSite> s_getEnumeratorSite =
CallSite>.Create(PSEnumerableBinder.Get());
///
/// Retrieves the IEnumerator of obj or null if the language does not consider obj as capable of returning an IEnumerator.
///
///
/// IEnumerable or IEnumerable-like object
///
/// When the act of getting the enumerator throws an exception.
[SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "Since V1 code is already shipped, excluding this message for backward compatibility reasons.")]
public static IEnumerator GetEnumerator(object obj)
{
var result = s_getEnumeratorSite.Target.Invoke(s_getEnumeratorSite, obj);
return (result is EnumerableOps.NonEnumerableObjectEnumerator) ? null : result;
}
#endregion GetEnumerable/GetEnumerator
///
/// This method takes a an arbitrary object and wraps it in a PSDataCollection of PSObject.
/// This simplifies interacting with the PowerShell workflow activities.
///
///
///
public static PSDataCollection GetPSDataCollection(object inputValue)
{
PSDataCollection result = new PSDataCollection();
if (inputValue != null)
{
IEnumerator e = GetEnumerator(inputValue);
if (e != null)
{
while (e.MoveNext())
{
result.Add(e.Current == null ? null : PSObject.AsPSObject(e.Current));
}
}
else
{
result.Add(PSObject.AsPSObject(inputValue));
}
}
result.Complete();
return result;
}
///
/// Used to compare two objects for equality converting the second to the type of the first, if required.
///
/// First object.
/// Object to compare first to.
/// True if first is equal to the second.
public static new bool Equals(object first, object second)
{
return Equals(first, second, false, CultureInfo.InvariantCulture);
}
///
/// Used to compare two objects for equality converting the second to the type of the first, if required.
///
/// First object.
/// Object to compare first to.
/// used only if first and second are strings
/// to specify the type of string comparison
/// True if first is equal to the second.
public static bool Equals(object first, object second, bool ignoreCase)
{
return Equals(first, second, ignoreCase, CultureInfo.InvariantCulture);
}
///
/// Used to compare two objects for equality converting the second to the type of the first, if required.
///
/// First object.
/// Object to compare first to.
/// used only if first and second are strings
/// to specify the type of string comparison
/// the format/culture to be used. If this parameter is null,
/// CultureInfo.InvariantCulture will be used.
///
/// True if first is equal to the second.
public static bool Equals(object first, object second, bool ignoreCase, IFormatProvider formatProvider)
{
// If both first and second are null it returns true.
// If one is null and the other is not it returns false.
// if (first.Equals(second)) it returns true otherwise it goes ahead with type conversion operations.
// If both first and second are strings it returns (string.Compare(firstString, secondString, ignoreCase) == 0).
// If second can be converted to the type of the first, it does so and returns first.Equals(secondConverted)
// Otherwise false is returned
if (formatProvider == null)
{
formatProvider = CultureInfo.InvariantCulture;
}
var culture = formatProvider as CultureInfo;
if (culture == null)
{
throw PSTraceSource.NewArgumentException("formatProvider");
}
first = PSObject.Base(first);
second = PSObject.Base(second);
if (first == null)
{
if (second == null) return true;
return false;
}
if (second == null)
{
return false; // first is not null
}
string firstString = first as string;
string secondString;
if (firstString != null)
{
secondString = second as string ?? (string)LanguagePrimitives.ConvertTo(second, typeof(string), culture);
return (culture.CompareInfo.Compare(firstString, secondString,
ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None) == 0);
}
if (first.Equals(second)) return true;
Type firstType = first.GetType();
Type secondType = second.GetType();
int firstIndex = LanguagePrimitives.TypeTableIndex(firstType);
int secondIndex = LanguagePrimitives.TypeTableIndex(secondType);
if ((firstIndex != -1) && (secondIndex != -1))
{
return LanguagePrimitives.NumericCompare(first, second, firstIndex, secondIndex) == 0;
}
if (firstType == typeof(char) && ignoreCase)
{
secondString = second as string;
if (secondString != null && secondString.Length == 1)
{
char firstAsUpper = culture.TextInfo.ToUpper((char)first);
char secondAsUpper = culture.TextInfo.ToUpper(secondString[0]);
return firstAsUpper.Equals(secondAsUpper);
}
else if (secondType == typeof(char))
{
char firstAsUpper = culture.TextInfo.ToUpper((char)first);
char secondAsUpper = culture.TextInfo.ToUpper((char)second);
return firstAsUpper.Equals(secondAsUpper);
}
}
try
{
object secondConverted = LanguagePrimitives.ConvertTo(second, firstType, culture);
return first.Equals(secondConverted);
}
catch (InvalidCastException)
{
}
return false;
}
///
/// Helper method for [Try]Compare to determine object ordering with null.
///
/// The numeric value to compare to null.
/// True if the number to compare is on the right hand side if the comparison.
private static int CompareObjectToNull(object value, bool numberIsRightHandSide)
{
var i = numberIsRightHandSide ? -1 : 1;
// If it's a positive number, including 0, it's greater than null
// for everything else it's less than zero...
switch (value)
{
case Int16 i16: return Math.Sign(i16) < 0 ? -i : i;
case Int32 i32: return Math.Sign(i32) < 0 ? -i : i;
case Int64 i64: return Math.Sign(i64) < 0 ? -i : i;
case sbyte sby: return Math.Sign(sby) < 0 ? -i : i;
case float f: return Math.Sign(f) < 0 ? -i : i;
case double d: return Math.Sign(d) < 0 ? -i : i;
case decimal de: return Math.Sign(de) < 0 ? -i : i;
default: return i;
}
}
///
/// Compare first and second, converting second to the
/// type of the first, if necessary.
///
/// First comparison value.
/// Second comparison value.
/// Less than zero if first is smaller than second, more than
/// zero if it is greater or zero if they are the same.
///
/// does not implement IComparable or cannot be converted
/// to the type of .
///
public static int Compare(object first, object second)
{
return LanguagePrimitives.Compare(first, second, false, CultureInfo.InvariantCulture);
}
///
/// Compare first and second, converting second to the
/// type of the first, if necessary.
///
/// First comparison value.
/// Second comparison value.
/// Used if both values are strings.
/// Less than zero if first is smaller than second, more than
/// zero if it is greater or zero if they are the same.
///
/// does not implement IComparable or cannot be converted
/// to the type of .
///
public static int Compare(object first, object second, bool ignoreCase)
{
return LanguagePrimitives.Compare(first, second, ignoreCase, CultureInfo.InvariantCulture);
}
///
/// Compare first and second, converting second to the
/// type of the first, if necessary.
///
/// First comparison value.
/// Second comparison value.
/// Used if both values are strings.
/// Used in type conversions and if both values are strings.
/// Less than zero if first is smaller than second, more than
/// zero if it is greater or zero if they are the same.
///
/// does not implement IComparable or cannot be converted
/// to the type of .
///
public static int Compare(object first, object second, bool ignoreCase, IFormatProvider formatProvider)
{
if (formatProvider == null)
{
formatProvider = CultureInfo.InvariantCulture;
}
var culture = formatProvider as CultureInfo;
if (culture == null)
{
throw PSTraceSource.NewArgumentException("formatProvider");
}
first = PSObject.Base(first);
second = PSObject.Base(second);
if (first == null)
{
return second == null ? 0 : CompareObjectToNull(second, true);
}
if (second == null)
{
return CompareObjectToNull(first, false);
}
if (first is string firstString)
{
string secondString = second as string;
if (secondString == null)
{
try
{
secondString = (string)LanguagePrimitives.ConvertTo(second, typeof(string), culture);
}
catch (PSInvalidCastException e)
{
throw PSTraceSource.NewArgumentException("second", ExtendedTypeSystem.ComparisonFailure,
first.ToString(), second.ToString(), e.Message);
}
}
return culture.CompareInfo.Compare(firstString, secondString,
ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
}
Type firstType = first.GetType();
Type secondType = second.GetType();
int firstIndex = LanguagePrimitives.TypeTableIndex(firstType);
int secondIndex = LanguagePrimitives.TypeTableIndex(secondType);
if ((firstIndex != -1) && (secondIndex != -1))
{
return LanguagePrimitives.NumericCompare(first, second, firstIndex, secondIndex);
}
object secondConverted;
try
{
secondConverted = LanguagePrimitives.ConvertTo(second, firstType, culture);
}
catch (PSInvalidCastException e)
{
throw PSTraceSource.NewArgumentException("second", ExtendedTypeSystem.ComparisonFailure,
first.ToString(), second.ToString(), e.Message);
}
if (first is IComparable firstComparable)
{
return firstComparable.CompareTo(secondConverted);
}
if (first.Equals(second))
{
return 0;
}
// At this point, we know that they aren't equal but we have no way of
// knowing which should compare greater than the other so we throw an exception.
throw PSTraceSource.NewArgumentException("first", ExtendedTypeSystem.NotIcomparable, first.ToString());
}
///
/// Tries to compare first and second, converting second to the type of the first, if necessary.
/// If a conversion is needed but fails, false is return.
///
/// First comparison value.
/// Second comparison value.
/// Less than zero if first is smaller than second, more than
/// zero if it is greater or zero if they are the same.
/// True if the comparison was successful, false otherwise.
public static bool TryCompare(object first, object second, out int result)
{
return TryCompare(first, second, ignoreCase: false, CultureInfo.InvariantCulture, out result);
}
///
/// Tries to compare first and second, converting second to the type of the first, if necessary.
/// If a conversion is needed but fails, false is return.
///
/// First comparison value.
/// Second comparison value.
/// Used if both values are strings.
/// Less than zero if first is smaller than second, more than zero if it is greater or zero if they are the same.
/// True if the comparison was successful, false otherwise.
public static bool TryCompare(object first, object second, bool ignoreCase, out int result)
{
return TryCompare(first, second, ignoreCase, CultureInfo.InvariantCulture, out result);
}
///
/// Tries to compare first and second, converting second to the type of the first, if necessary.
/// If a conversion is needed but fails, false is return.
///
/// First comparison value.
/// Second comparison value.
/// Used if both values are strings.
/// Used in type conversions and if both values are strings.
/// Less than zero if first is smaller than second, more than zero if it is greater or zero if they are the same.
/// True if the comparison was successful, false otherwise.
/// The parameter is not a .
public static bool TryCompare(object first, object second, bool ignoreCase, IFormatProvider formatProvider, out int result)
{
result = 0;
if (formatProvider == null)
{
formatProvider = CultureInfo.InvariantCulture;
}
if (!(formatProvider is CultureInfo culture))
{
throw PSTraceSource.NewArgumentException("formatProvider");
}
first = PSObject.Base(first);
second = PSObject.Base(second);
if (first == null && second == null)
{
result = 0;
return true;
}
if (first == null)
{
result = CompareObjectToNull(second, true);
return true;
}
if (second == null)
{
// If it's a positive number, including 0, it's greater than null
// for everything else it's less than zero...
result = CompareObjectToNull(first, false);
return true;
}
if (first is string firstString)
{
if (!(second is string secondString))
{
if (!TryConvertTo(second, culture, out secondString))
{
return false;
}
}
result = culture.CompareInfo.Compare(firstString, secondString, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
return true;
}
Type firstType = first.GetType();
Type secondType = second.GetType();
int firstIndex = TypeTableIndex(firstType);
int secondIndex = TypeTableIndex(secondType);
if (firstIndex != -1 && secondIndex != -1)
{
result = NumericCompare(first, second, firstIndex, secondIndex);
return true;
}
if (!TryConvertTo(second, firstType, culture, out object secondConverted))
{
return false;
}
if (first is IComparable firstComparable)
{
result = firstComparable.CompareTo(secondConverted);
return true;
}
if (first.Equals(second))
{
result = 0;
return true;
}
// At this point, we know that they aren't equal but we have no way of
// knowing which should compare greater than the other so we return false.
return false;
}
///
/// Returns true if the language considers obj to be true.
///
/// Obj to verify if it is true.
/// True if obj is true.
[SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "obj", Justification = "Since V1 code is already shipped, excluding this message for backward compatibility reasons")]
public static bool IsTrue(object obj)
{
// null is a valid argument - it converts to false...
if (obj == null || obj == AutomationNull.Value)
return false;
obj = PSObject.Base(obj);
Type objType = obj.GetType();
if (objType == typeof(bool))
{
return (bool)obj;
}
if (objType == typeof(string))
{
return IsTrue((string)obj);
}
if (LanguagePrimitives.IsNumeric(LanguagePrimitives.GetTypeCode(objType)))
{
IConversionData data = GetConversionData(objType, typeof(bool)) ??
CacheConversion(objType, typeof(bool), CreateNumericToBoolConverter(objType), ConversionRank.Language);
return (bool)data.Invoke(obj, typeof(bool), false, null, null, null);
}
if (objType == typeof(SwitchParameter))
return ((SwitchParameter)obj).ToBool();
IList objectArray = obj as IList;
if (objectArray != null)
{
return IsTrue(objectArray);
}
return true;
}
internal static bool IsTrue(string s)
{
return (s.Length != 0);
}
internal static bool IsTrue(IList objectArray)
{
switch (objectArray.Count)
{
// a zero length array is false, so condition is false
case 0:
return false;
// if the result is an array of length 1, treat it as a scalar...
case 1:
// A possible implementation would be just
// return IsTrue(objectArray[0]);
// but since we don't want this to recurse indefinitely
// we explicitly check the case where it would recurse
// and deal with it.
IList firstElement = PSObject.Base(objectArray[0]) as IList;
if (firstElement == null)
{
return IsTrue(objectArray[0]);
}
if (firstElement.Count < 1) return false;
// the first element is an array with more than zero elements
return true;
default:
return true;
}
}
///
/// Internal routine that determines if an object meets any of our criteria for null.
///
/// The object to test.
/// True if the object is null.
internal static bool IsNull(object obj)
{
return (obj == null || obj == AutomationNull.Value);
}
///
/// Auxiliary for the cases where we want a new PSObject or null.
///
internal static PSObject AsPSObjectOrNull(object obj)
{
if (obj == null)
{
return null;
}
return PSObject.AsPSObject(obj);
}
internal static int TypeTableIndex(Type type)
{
switch (LanguagePrimitives.GetTypeCode(type))
{
case TypeCode.Int16: return 0;
case TypeCode.Int32: return 1;
case TypeCode.Int64: return 2;
case TypeCode.UInt16: return 3;
case TypeCode.UInt32: return 4;
case TypeCode.UInt64: return 5;
case TypeCode.SByte: return 6;
case TypeCode.Byte: return 7;
case TypeCode.Single: return 8;
case TypeCode.Double: return 9;
case TypeCode.Decimal: return 10;
default: return -1;
}
}
///
/// Table of the largest safe type to which both types can be converted without exceptions.
/// This table is used for numeric comparisons.
/// The 4 entries marked as not used, are explicitly dealt with in NumericCompareDecimal.
/// NumericCompareDecimal exists because doubles and singles can throw
/// an exception when converted to decimal.
/// The order of lines and columns cannot be changed since NumericCompare depends on it.
///
internal static Type[][] LargestTypeTable = new Type[][]
{
// System.Int16 System.Int32 System.Int64 System.UInt16 System.UInt32 System.UInt64 System.SByte System.Byte System.Single System.Double System.Decimal
/* System.Int16 */new Type[] { typeof(System.Int16), typeof(System.Int32), typeof(System.Int64), typeof(System.Int32), typeof(System.Int64), typeof(System.Double), typeof(System.Int16), typeof(System.Int16), typeof(System.Single), typeof(System.Double), typeof(System.Decimal) },
/* System.Int32 */new Type[] { typeof(System.Int32), typeof(System.Int32), typeof(System.Int64), typeof(System.Int32), typeof(System.Int64), typeof(System.Double), typeof(System.Int32), typeof(System.Int32), typeof(System.Double), typeof(System.Double), typeof(System.Decimal) },
/* System.Int64 */new Type[] { typeof(System.Int64), typeof(System.Int64), typeof(System.Int64), typeof(System.Int64), typeof(System.Int64), typeof(System.Decimal), typeof(System.Int64), typeof(System.Int64), typeof(System.Double), typeof(System.Double), typeof(System.Decimal) },
/* System.UInt16 */new Type[] { typeof(System.Int32), typeof(System.Int32), typeof(System.Int64), typeof(System.UInt16), typeof(System.UInt32), typeof(System.UInt64), typeof(System.Int32), typeof(System.UInt16), typeof(System.Single), typeof(System.Double), typeof(System.Decimal) },
/* System.UInt32 */new Type[] { typeof(System.Int64), typeof(System.Int64), typeof(System.Int64), typeof(System.UInt32), typeof(System.UInt32), typeof(System.UInt64), typeof(System.Int64), typeof(System.UInt32), typeof(System.Double), typeof(System.Double), typeof(System.Decimal) },
/* System.UInt64 */new Type[] { typeof(System.Double), typeof(System.Double), typeof(System.Decimal), typeof(System.UInt64), typeof(System.UInt64), typeof(System.UInt64), typeof(System.Double), typeof(System.UInt64), typeof(System.Double), typeof(System.Double), typeof(System.Decimal) },
/* System.SByte */new Type[] { typeof(System.Int16), typeof(System.Int32), typeof(System.Int64), typeof(System.Int32), typeof(System.Int64), typeof(System.Double), typeof(System.SByte), typeof(System.Int16), typeof(System.Single), typeof(System.Double), typeof(System.Decimal) },
/* System.Byte */new Type[] { typeof(System.Int16), typeof(System.Int32), typeof(System.Int64), typeof(System.UInt16), typeof(System.UInt32), typeof(System.UInt64), typeof(System.Int16), typeof(System.Byte), typeof(System.Single), typeof(System.Double), typeof(System.Decimal) },
/* System.Single */new Type[] { typeof(System.Single), typeof(System.Double), typeof(System.Double), typeof(System.Single), typeof(System.Double), typeof(System.Double), typeof(System.Single), typeof(System.Single), typeof(System.Single), typeof(System.Double), null/*not used*/ },
/* System.Double */new Type[] { typeof(System.Double), typeof(System.Double), typeof(System.Double), typeof(System.Double), typeof(System.Double), typeof(System.Double), typeof(System.Double), typeof(System.Double), typeof(System.Double), typeof(System.Double), null/*not used*/ },
/* System.Decimal */new Type[] { typeof(System.Decimal), typeof(System.Decimal), typeof(System.Decimal), typeof(System.Decimal), typeof(System.Decimal), typeof(System.Decimal), typeof(System.Decimal), typeof(System.Decimal), null/*not used*/, null/*not used*/, typeof(System.Decimal) },
};
private static int NumericCompareDecimal(decimal decimalNumber, object otherNumber)
{
object otherDecimal = null;
try
{
otherDecimal = Convert.ChangeType(otherNumber, typeof(System.Decimal), CultureInfo.InvariantCulture);
}
catch (OverflowException)
{
try
{
double wasDecimal = (double)Convert.ChangeType(decimalNumber, typeof(System.Double), CultureInfo.InvariantCulture);
double otherDouble = (double)Convert.ChangeType(otherNumber, typeof(System.Double), CultureInfo.InvariantCulture);
return ((IComparable)wasDecimal).CompareTo(otherDouble);
}
catch (Exception) // We need to catch the generic exception because ChangeType throws unadvertised exceptions
{
return -1;
}
}
catch (Exception) // We need to catch the generic exception because ChangeType throws unadvertised exceptions
{
return -1;
}
return ((IComparable)decimalNumber).CompareTo(otherDecimal);
}
private static int NumericCompare(object number1, object number2, int index1, int index2)
{
// Conversion from single or double to decimal might throw
// if the double is greater than the decimal's maximum so
// we special case it in NumericCompareDecimal
if ((index1 == 10) && ((index2 == 8) || (index2 == 9)))
{
return NumericCompareDecimal((decimal)number1, number2);
}
if ((index2 == 10) && ((index1 == 8) || (index1 == 9)))
{
return -NumericCompareDecimal((decimal)number2, number1);
}
Type commonType = LargestTypeTable[index1][index2];
object number1Converted = Convert.ChangeType(number1, commonType, CultureInfo.InvariantCulture);
object number2Converted = Convert.ChangeType(number2, commonType, CultureInfo.InvariantCulture);
return ((IComparable)number1Converted).CompareTo(number2Converted);
}
///
/// Necessary not to return an integer type code for enums.
///
///
///
internal static TypeCode GetTypeCode(Type type)
{
return type.GetTypeCode();
}
///
/// Emulates the "As" C# language primitive, but will unwrap
/// the PSObject if required.
///
/// The type for which to convert
/// The object from which to convert.
/// An object of the specified type, if the conversion was successful. Returns null otherwise.
internal static T FromObjectAs(Object castObject)
{
T returnType = default(T);
// First, see if we can cast the direct type
PSObject wrapperObject = castObject as PSObject;
if (wrapperObject == null)
{
try
{
returnType = (T)castObject;
}
catch (InvalidCastException)
{
returnType = default(T);
}
}
// Then, see if it is an PSObject wrapping the object
else
{
try
{
returnType = (T)wrapperObject.BaseObject;
}
catch (InvalidCastException)
{
returnType = default(T);
}
}
return returnType;
}
[Flags]
private enum TypeCodeTraits
{
None = 0x00,
SignedInteger = 0x01,
UnsignedInteger = 0x02,
Floating = 0x04,
CimIntrinsicType = 0x08,
Decimal = 0x10,
Integer = SignedInteger | UnsignedInteger,
Numeric = Integer | Floating | Decimal,
}
private static readonly TypeCodeTraits[] s_typeCodeTraits = new TypeCodeTraits[]
{
/* Empty = 0 */ TypeCodeTraits.None,
/* Object = 1 */ TypeCodeTraits.None,
/* DBNull = 2 */ TypeCodeTraits.None,
/* Boolean = 3 */ TypeCodeTraits.CimIntrinsicType,
/* Char = 4 */ TypeCodeTraits.CimIntrinsicType,
/* SByte = 5 */ TypeCodeTraits.SignedInteger | TypeCodeTraits.CimIntrinsicType,
/* Byte = 6 */ TypeCodeTraits.UnsignedInteger | TypeCodeTraits.CimIntrinsicType,
/* Int16 = 7 */ TypeCodeTraits.SignedInteger | TypeCodeTraits.CimIntrinsicType,
/* UInt16 = 8 */ TypeCodeTraits.UnsignedInteger | TypeCodeTraits.CimIntrinsicType,
/* Int32 = 9 */ TypeCodeTraits.SignedInteger | TypeCodeTraits.CimIntrinsicType,
/* UInt32 = 10 */ TypeCodeTraits.UnsignedInteger | TypeCodeTraits.CimIntrinsicType,
/* Int64 = 11 */ TypeCodeTraits.SignedInteger | TypeCodeTraits.CimIntrinsicType,
/* UInt64 = 12 */ TypeCodeTraits.UnsignedInteger | TypeCodeTraits.CimIntrinsicType,
/* Single = 13 */ TypeCodeTraits.Floating | TypeCodeTraits.CimIntrinsicType,
/* Double = 14 */ TypeCodeTraits.Floating | TypeCodeTraits.CimIntrinsicType,
/* Decimal = 15 */ TypeCodeTraits.Decimal,
/* DateTime = 16 */ TypeCodeTraits.None | TypeCodeTraits.CimIntrinsicType,
/* = 17 */ TypeCodeTraits.None,
/* String = 18 */ TypeCodeTraits.None | TypeCodeTraits.CimIntrinsicType,
};
///
/// Verifies if type is a signed integer.
///
/// Type code to check.
/// True if type is a signed integer, false otherwise.
internal static bool IsSignedInteger(TypeCode typeCode)
{
return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.SignedInteger) != 0;
}
///
/// Verifies if type is an unsigned integer.
///
/// Type code to check.
/// True if type is an unsigned integer, false otherwise.
internal static bool IsUnsignedInteger(TypeCode typeCode)
{
return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.UnsignedInteger) != 0;
}
///
/// Verifies if type is integer.
///
/// Type code to check.
/// True if type is integer, false otherwise.
internal static bool IsInteger(TypeCode typeCode)
{
return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.Integer) != 0;
}
///
/// Verifies if type is a floating point number.
///
/// Type code to check.
/// True if type is floating point, false otherwise.
internal static bool IsFloating(TypeCode typeCode)
{
return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.Floating) != 0;
}
///
/// Verifies if type is an integer or floating point number.
///
/// Type code to check.
/// True if type is integer or floating point, false otherwise.
internal static bool IsNumeric(TypeCode typeCode)
{
return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.Numeric) != 0;
}
///
/// Verifies if type is a CIM intrinsic type.
///
/// Type code to check.
/// True if type is CIM intrinsic type, false otherwise.
internal static bool IsCimIntrinsicScalarType(TypeCode typeCode)
{
return (s_typeCodeTraits[(int)typeCode] & TypeCodeTraits.CimIntrinsicType) != 0;
}
internal static bool IsCimIntrinsicScalarType(Type type)
{
Dbg.Assert(type != null, "Caller should verify type != null");
// using type code we can cover all intrinsic types from the table
// on page 11 of DSP0004, except:
// - TimeSpan part of "datetime"
// - ref
TypeCode typeCode = LanguagePrimitives.GetTypeCode(type);
if (LanguagePrimitives.IsCimIntrinsicScalarType(typeCode) && !type.IsEnum)
{
return true;
}
if (type == typeof(TimeSpan))
{
return true;
}
return false;
}
///
/// Verifies if type is one of the boolean types.
///
/// Type to check.
/// True if type is one of boolean types, false otherwise.
internal static bool IsBooleanType(Type type)
{
if (type == typeof(bool) ||
type == typeof(bool?))
return true;
else
return false;
}
///
/// Verifies if type is one of switch parameter types.
///
/// Type to check.
/// True if type is one of switch parameter types, false otherwise.
internal static bool IsSwitchParameterType(Type type)
{
if (type == typeof(SwitchParameter) || type == typeof(SwitchParameter?))
return true;
else
return false;
}
///
/// Verifies if type is one of boolean or switch parameter types.
///
/// Type to check.
/// True if type if one of boolean or switch parameter types,
/// false otherwise.
internal static bool IsBoolOrSwitchParameterType(Type type)
{
if (IsBooleanType(type) || IsSwitchParameterType(type))
return true;
else
return false;
}
///
/// Do the necessary conversions when using property or array assignment to a generic dictionary:
///
/// $dict.Prop = value
/// $dict[$Prop] = value
///
/// The property typically won't need conversion, but it could. The value is more likely in
/// need of conversion.
///
/// The dictionary that potentially implement
/// The object representing the key.
/// The value to assign.
internal static void DoConversionsForSetInGenericDictionary(IDictionary dictionary, ref object key, ref object value)
{
foreach (Type i in dictionary.GetType().GetInterfaces())
{
if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
// If we get here, we know the target implements IDictionary. We will assume
// that the non-generic implementation of the indexer property just forwards
// to the generic version, after checking the types of the key and value.
// This assumption holds for System.Collections.Generic.Dictionary.
// If we did not make this assumption, we would be forced to generate code
// to call the generic indexer directly, somewhat analogous to what we do
// in GetEnumeratorFromIEnumeratorT.
Type[] genericArguments = i.GetGenericArguments();
key = LanguagePrimitives.ConvertTo(key, genericArguments[0], CultureInfo.InvariantCulture);
value = LanguagePrimitives.ConvertTo(value, genericArguments[1], CultureInfo.InvariantCulture);
}
}
}
#region type converter
internal static PSTraceSource typeConversion = PSTraceSource.GetTracer("TypeConversion", "Traces the type conversion algorithm", false);
internal static ConversionData