/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System.Diagnostics;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.Management.Automation.Internal;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Win32;
namespace System.Management.Automation
{
///
/// Encapsulates $PSVersionTable.
///
internal class PSVersionInfo
{
internal const string PSVersionTableName = "PSVersionTable";
internal const string PSRemotingProtocolVersionName = "PSRemotingProtocolVersion";
internal const string PSVersionName = "PSVersion";
internal const string PSEditionName = "PSEdition";
internal const string PSGitCommitIdName = "GitCommitId";
internal const string PSCompatibleVersionsName = "PSCompatibleVersions";
internal const string PSPlatformName = "Platform";
internal const string PSOSName = "OS";
internal const string SerializationVersionName = "SerializationVersion";
internal const string WSManStackVersionName = "WSManStackVersion";
private static PSVersionHashTable s_psVersionTable = null;
///
/// A constant to track current PowerShell Version.
///
///
/// We can't depend on assembly version for PowerShell version.
///
/// This is why we hard code the PowerShell version here.
///
/// For each later release of PowerShell, this constant needs to
/// be updated to reflect the right version.
///
private static Version s_psV1Version = new Version(1, 0);
private static Version s_psV2Version = new Version(2, 0);
private static Version s_psV3Version = new Version(3, 0);
private static Version s_psV4Version = new Version(4, 0);
private static Version s_psV5Version = new Version(5, 0);
private static Version s_psV51Version = new Version(5, 1, NTVerpVars.PRODUCTBUILD, NTVerpVars.PRODUCTBUILD_QFE);
private static SemanticVersion s_psV6Version;
///
/// A constant to track current PowerShell Edition
///
internal const string PSEditionValue = "Core";
// Static Constructor.
static PSVersionInfo()
{
s_psVersionTable = new PSVersionHashTable(StringComparer.OrdinalIgnoreCase);
string assemblyPath = typeof(PSVersionInfo).Assembly.Location;
string productVersion = FileVersionInfo.GetVersionInfo(assemblyPath).ProductVersion;
// Get 'GitCommitId' and 'PSVersion' from the 'productVersion' assembly attribute.
//
// The strings can be one of the following format examples:
// when powershell is built from a commit:
// productVersion = '6.0.0-beta.7 Commits: 29 SHA: 52c6b...' convert to GitCommitId = 'v6.0.0-beta.7-29-g52c6b...'
// PSVersion = '6.0.0-beta.7'
// when powershell is built from a release tag:
// productVersion = '6.0.0-beta.7 SHA: f1ec9...' convert to GitCommitId = 'v6.0.0-beta.7'
// PSVersion = '6.0.0-beta.7'
// when powershell is built from a release tag for RTM:
// productVersion = '6.0.0 SHA: f1ec9...' convert to GitCommitId = 'v6.0.0'
// PSVersion = '6.0.0'
string rawGitCommitId;
string mainVersion = productVersion.Substring(0, productVersion.IndexOf(' '));
if (productVersion.Contains(" Commits: "))
{
rawGitCommitId = "v" + productVersion.Replace(" Commits: ", "-").Replace(" SHA: ", "-g");
}
else
{
rawGitCommitId = "v" + mainVersion;
}
s_psV6Version = new SemanticVersion(mainVersion);
s_psVersionTable[PSVersionInfo.PSVersionName] = s_psV6Version;
s_psVersionTable[PSVersionInfo.PSEditionName] = PSEditionValue;
s_psVersionTable[PSGitCommitIdName] = rawGitCommitId;
s_psVersionTable[PSCompatibleVersionsName] = new Version[] { s_psV1Version, s_psV2Version, s_psV3Version, s_psV4Version, s_psV5Version, s_psV51Version, s_psV6Version };
s_psVersionTable[PSVersionInfo.SerializationVersionName] = new Version(InternalSerializer.DefaultVersion);
s_psVersionTable[PSVersionInfo.PSRemotingProtocolVersionName] = RemotingConstants.ProtocolVersion;
s_psVersionTable[PSVersionInfo.WSManStackVersionName] = GetWSManStackVersion();
s_psVersionTable[PSPlatformName] = Environment.OSVersion.Platform.ToString();
s_psVersionTable[PSOSName] = Runtime.InteropServices.RuntimeInformation.OSDescription.ToString();
}
internal static PSVersionHashTable GetPSVersionTable()
{
return s_psVersionTable;
}
internal static Hashtable GetPSVersionTableForDownLevel()
{
var result = (Hashtable)s_psVersionTable.Clone();
// Downlevel systems don't support SemanticVersion, but Version is most likely good enough anyway.
result[PSVersionInfo.PSVersionName] = (Version)(SemanticVersion)s_psVersionTable[PSVersionInfo.PSVersionName];
return result;
}
#region Private helper methods
// Gets the current WSMan stack version from the registry.
private static Version GetWSManStackVersion()
{
Version version = null;
#if !UNIX
try
{
using (RegistryKey wsManStackVersionKey = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\WSMAN"))
{
if (wsManStackVersionKey != null)
{
object wsManStackVersionObj = wsManStackVersionKey.GetValue("ServiceStackVersion");
string wsManStackVersion = (wsManStackVersionObj != null) ? (string)wsManStackVersionObj : null;
if (!string.IsNullOrEmpty(wsManStackVersion))
{
version = new Version(wsManStackVersion.Trim());
}
}
}
}
catch (ObjectDisposedException) { }
catch (System.Security.SecurityException) { }
catch (ArgumentException) { }
catch (System.IO.IOException) { }
catch (UnauthorizedAccessException) { }
catch (FormatException) { }
catch (OverflowException) { }
catch (InvalidCastException) { }
#endif
return version ?? System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STACK_VERSION;
}
#endregion
#region Programmer APIs
internal static Version PSVersion
{
get
{
return (SemanticVersion)GetPSVersionTable()[PSVersionInfo.PSVersionName];
}
}
internal static string GitCommitId
{
get
{
return (string)GetPSVersionTable()[PSGitCommitIdName];
}
}
internal static Version[] PSCompatibleVersions
{
get
{
return (Version[])GetPSVersionTable()[PSCompatibleVersionsName];
}
}
internal static string PSEdition
{
get
{
return (string)GetPSVersionTable()[PSVersionInfo.PSEditionName];
}
}
internal static Version SerializationVersion
{
get
{
return (Version)GetPSVersionTable()[SerializationVersionName];
}
}
///
///
///
///
/// For 2.0 PowerShell, we still use "1" as the registry version key.
/// For >=3.0 PowerShell, we still use "1" as the registry version key for
/// Snapin and Custom shell lookup/discovery.
///
internal static string RegistryVersion1Key
{
get
{
return "1";
}
}
///
///
///
///
/// For 3.0 PowerShell, we use "3" as the registry version key only for Engine
/// related data like ApplicationBase.
/// For 3.0 PowerShell, we still use "1" as the registry version key for
/// Snapin and Custom shell lookup/discovery.
///
internal static string RegistryVersionKey
{
get
{
// PowerShell >=4 is compatible with PowerShell 3 and hence reg key is 3.
return "3";
}
}
internal static string GetRegistryVersionKeyForSnapinDiscovery(string majorVersion)
{
int tempMajorVersion = 0;
LanguagePrimitives.TryConvertTo(majorVersion, out tempMajorVersion);
if ((tempMajorVersion >= 1) && (tempMajorVersion <= PSVersionInfo.PSVersion.Major))
{
// PowerShell version 3 took a dependency on CLR4 and went with:
// SxS approach in GAC/Registry and in-place upgrade approach for
// FileSystem.
// For >=3.0 PowerShell, we still use "1" as the registry version key for
// Snapin and Custom shell lookup/discovery.
return "1";
}
return null;
}
internal static string FeatureVersionString
{
get
{
return String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}.{1}", PSVersionInfo.PSVersion.Major, PSVersionInfo.PSVersion.Minor);
}
}
internal static bool IsValidPSVersion(Version version)
{
if (version.Major == s_psV6Version.Major)
{
return version.Minor == s_psV6Version.Minor;
}
if (version.Major == s_psV5Version.Major)
{
return (version.Minor == s_psV5Version.Minor || version.Minor == s_psV51Version.Minor);
}
if (version.Major == s_psV4Version.Major)
{
return (version.Minor == s_psV4Version.Minor);
}
else if (version.Major == s_psV3Version.Major)
{
return version.Minor == s_psV3Version.Minor;
}
else if (version.Major == s_psV2Version.Major)
{
return version.Minor == s_psV2Version.Minor;
}
else if (version.Major == s_psV1Version.Major)
{
return version.Minor == s_psV1Version.Minor;
}
return false;
}
internal static Version PSV4Version
{
get { return s_psV4Version; }
}
internal static Version PSV5Version
{
get { return s_psV5Version; }
}
internal static Version PSV51Version
{
get { return s_psV51Version; }
}
internal static SemanticVersion PSV6Version
{
get { return s_psV6Version; }
}
#endregion
}
///
/// Represents an implementation of '$PSVersionTable' variable.
/// The implementation contains ordered 'Keys' and 'GetEnumerator' to get user-friendly output.
///
public sealed class PSVersionHashTable : Hashtable, IEnumerable
{
private static readonly PSVersionTableComparer s_keysComparer = new PSVersionTableComparer();
internal PSVersionHashTable(IEqualityComparer equalityComparer) : base(equalityComparer)
{
}
///
/// Returns ordered collection with Keys of 'PSVersionHashTable'
/// We want see special order:
/// 1. PSVersionName
/// 2. PSEditionName
/// 3. Remaining properties in alphabetical order
///
public override ICollection Keys
{
get
{
ArrayList keyList = new ArrayList(base.Keys);
keyList.Sort(s_keysComparer);
return keyList;
}
}
private class PSVersionTableComparer : IComparer
{
public int Compare(object x, object y)
{
string xString = (string)LanguagePrimitives.ConvertTo(x, typeof(string), CultureInfo.CurrentCulture);
string yString = (string)LanguagePrimitives.ConvertTo(y, typeof(string), CultureInfo.CurrentCulture);
if (PSVersionInfo.PSVersionName.Equals(xString, StringComparison.OrdinalIgnoreCase))
{
return -1;
}
else if (PSVersionInfo.PSVersionName.Equals(yString, StringComparison.OrdinalIgnoreCase))
{
return 1;
}
else if (PSVersionInfo.PSEditionName.Equals(xString, StringComparison.OrdinalIgnoreCase))
{
return -1;
}
else if (PSVersionInfo.PSEditionName.Equals(yString, StringComparison.OrdinalIgnoreCase))
{
return 1;
}
else
{
return String.Compare(xString, yString, StringComparison.OrdinalIgnoreCase);
}
}
}
///
/// Returns an enumerator for 'PSVersionHashTable'.
/// The enumeration is ordered (based on ordered version of 'Keys').
///
IEnumerator IEnumerable.GetEnumerator()
{
foreach (object key in Keys)
{
yield return new DictionaryEntry(key, this[key]);
}
}
}
///
/// An implementation of semantic versioning (http://semver.org)
/// that can be converted to/from .
///
/// When converting to , a PSNoteProperty is
/// added to the instance to store the semantic version label so
/// that it can be recovered when creating a new SemanticVersion.
///
public sealed class SemanticVersion : IComparable, IComparable, IEquatable
{
private const string VersionSansRegEx = @"^(?\d+)(\.(?\d+))?(\.(?\d+))?$";
private const string LabelRegEx = @"^((?[0-9A-Za-z][0-9A-Za-z\-\.]*))?(\+(?[0-9A-Za-z][0-9A-Za-z\-\.]*))?$";
private const string LabelUnitRegEx = @"^[0-9A-Za-z][0-9A-Za-z\-\.]*$";
private const string PreLabelPropertyName = "PSSemVerPreReleaseLabel";
private const string BuildLabelPropertyName = "PSSemVerBuildLabel";
private const string TypeNameForVersionWithLabel = "System.Version#IncludeLabel";
private string versionString;
///
/// Construct a SemanticVersion from a string.
///
/// The version to parse
///
///
public SemanticVersion(string version)
{
var v = SemanticVersion.Parse(version);
Major = v.Major;
Minor = v.Minor;
Patch = v.Patch < 0 ? 0 : v.Patch;
PreReleaseLabel = v.PreReleaseLabel;
BuildLabel = v.BuildLabel;
}
///
/// Construct a SemanticVersion.
///
/// The major version
/// The minor version
/// The patch version
/// The pre-release label for the version
/// The build metadata for the version
///
/// If don't match 'LabelUnitRegEx'.
/// If don't match 'LabelUnitRegEx'.
///
public SemanticVersion(int major, int minor, int patch, string preReleaseLabel, string buildLabel)
: this(major, minor, patch)
{
if (!string.IsNullOrEmpty(preReleaseLabel))
{
if (!Regex.IsMatch(preReleaseLabel, LabelUnitRegEx)) throw new FormatException(nameof(preReleaseLabel));
PreReleaseLabel = preReleaseLabel;
}
if (!string.IsNullOrEmpty(buildLabel))
{
if (!Regex.IsMatch(buildLabel, LabelUnitRegEx)) throw new FormatException(nameof(buildLabel));
BuildLabel = buildLabel;
}
}
///
/// Construct a SemanticVersion.
///
/// The major version
/// The minor version
/// The minor version
/// The label for the version
///
///
/// If don't match 'LabelRegEx'.
///
public SemanticVersion(int major, int minor, int patch, string label)
: this(major, minor, patch)
{
// We presume the SymVer :
// 1) major.minor.patch-label
// 2) 'label' starts with letter or digit.
if (!string.IsNullOrEmpty(label))
{
var match = Regex.Match(label, LabelRegEx);
if (!match.Success) throw new FormatException(nameof(label));
PreReleaseLabel = match.Groups["preLabel"].Value;
BuildLabel = match.Groups["buildLabel"].Value;
}
}
///
/// Construct a SemanticVersion.
///
/// The major version
/// The minor version
/// The minor version
///
/// If , , or is less than 0.
///
public SemanticVersion(int major, int minor, int patch)
{
if (major < 0) throw PSTraceSource.NewArgumentException(nameof(major));
if (minor < 0) throw PSTraceSource.NewArgumentException(nameof(minor));
if (patch < 0) throw PSTraceSource.NewArgumentException(nameof(patch));
Major = major;
Minor = minor;
Patch = patch;
// We presume:
// PreReleaseLabel = null;
// BuildLabel = null;
}
///
/// Construct a SemanticVersion.
///
/// The major version
/// The minor version
///
/// If or is less than 0.
///
public SemanticVersion(int major, int minor) : this(major, minor, 0) {}
///
/// Construct a SemanticVersion.
///
/// The major version
///
/// If is less than 0.
///
public SemanticVersion(int major) : this(major, 0, 0) {}
///
/// Construct a from a ,
/// copying the NoteProperty storing the label if the expected property exists.
///
/// The version.
///
/// If is null.
///
///
/// If is more than 0.
///
public SemanticVersion(Version version)
{
if (version == null) throw PSTraceSource.NewArgumentNullException(nameof(version));
if (version.Revision > 0) throw PSTraceSource.NewArgumentException(nameof(version));
Major = version.Major;
Minor = version.Minor;
Patch = version.Build == -1 ? 0 : version.Build;
var psobj = new PSObject(version);
var preLabelNote = psobj.Properties[PreLabelPropertyName];
if (preLabelNote != null)
{
PreReleaseLabel = preLabelNote.Value as string;
}
var buildLabelNote = psobj.Properties[BuildLabelPropertyName];
if (buildLabelNote != null)
{
BuildLabel = buildLabelNote.Value as string;
}
}
///
/// Convert a to a .
/// If there is a or/and a ,
/// it is added as a NoteProperty to the result so that you can round trip
/// back to a without losing the label.
///
///
public static implicit operator Version(SemanticVersion semver)
{
PSObject psobj;
var result = new Version(semver.Major, semver.Minor, semver.Patch);
if (!string.IsNullOrEmpty(semver.PreReleaseLabel) || !string.IsNullOrEmpty(semver.BuildLabel))
{
psobj = new PSObject(result);
if (!string.IsNullOrEmpty(semver.PreReleaseLabel))
{
psobj.Properties.Add(new PSNoteProperty(PreLabelPropertyName, semver.PreReleaseLabel));
}
if (!string.IsNullOrEmpty(semver.BuildLabel))
{
psobj.Properties.Add(new PSNoteProperty(BuildLabelPropertyName, semver.BuildLabel));
}
psobj.TypeNames.Insert(0, TypeNameForVersionWithLabel);
}
return result;
}
///
/// The major version number, never negative.
///
public int Major { get; }
///
/// The minor version number, never negative.
///
public int Minor { get; }
///
/// The patch version, -1 if not specified.
///
public int Patch { get; }
///
/// PreReleaseLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'.
///
public string PreReleaseLabel { get; }
///
/// BuildLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'.
///
public string BuildLabel { get; }
///
/// Parse and return the result if it is a valid , otherwise throws an exception.
///
/// The string to parse
///
///
///
///
public static SemanticVersion Parse(string version)
{
if (version == null) throw PSTraceSource.NewArgumentNullException(nameof(version));
if (version == String.Empty) throw new FormatException(nameof(version));
var r = new VersionResult();
r.Init(true);
TryParseVersion(version, ref r);
return r._parsedVersion;
}
///
/// Parse and return true if it is a valid , otherwise return false.
/// No exceptions are raised.
///
/// The string to parse
/// The return value when the string is a valid
public static bool TryParse(string version, out SemanticVersion result)
{
if (version != null)
{
var r = new VersionResult();
r.Init(false);
if (TryParseVersion(version, ref r))
{
result = r._parsedVersion;
return true;
}
}
result = null;
return false;
}
private static bool TryParseVersion(string version, ref VersionResult result)
{
if (version.EndsWith('-') || version.EndsWith('+') || version.EndsWith('.'))
{
result.SetFailure(ParseFailureKind.FormatException);
return false;
}
string versionSansLabel = null;
var major=0;
var minor=0;
var patch=0;
string preLabel = null;
string buildLabel = null;
// We parse the SymVer 'version' string 'major.minor.patch-PreReleaseLabel+BuildLabel'.
var dashIndex = version.IndexOf('-');
var plusIndex = version.IndexOf('+');
if (dashIndex > plusIndex)
{
// 'PreReleaseLabel' can contains dashes.
if (plusIndex == -1)
{
// No buildLabel: buildLabel == null
// Format is 'major.minor.patch-PreReleaseLabel'
preLabel = version.Substring(dashIndex+1);
versionSansLabel = version.Substring(0, dashIndex);
}
else
{
// No PreReleaseLabel: preLabel == null
// Format is 'major.minor.patch+BuildLabel'
buildLabel = version.Substring(plusIndex+1);
versionSansLabel = version.Substring(0, plusIndex);
dashIndex = -1;
}
}
else
{
if (dashIndex == -1)
{
// Here dashIndex == plusIndex == -1
// No preLabel - preLabel == null;
// No buildLabel - buildLabel == null;
// Format is 'major.minor.patch'
versionSansLabel = version;
}
else
{
// Format is 'major.minor.patch-PreReleaseLabel+BuildLabel'
preLabel = version.Substring(dashIndex+1, plusIndex-dashIndex-1);
buildLabel = version.Substring(plusIndex+1);
versionSansLabel = version.Substring(0, dashIndex);
}
}
if ((dashIndex != - 1 && String.IsNullOrEmpty(preLabel)) ||
(plusIndex != - 1 && String.IsNullOrEmpty(buildLabel)) ||
String.IsNullOrEmpty(versionSansLabel))
{
// We have dash and no preReleaseLabel or
// we have plus and no buildLabel or
// we have no main version part (versionSansLabel==null)
result.SetFailure(ParseFailureKind.FormatException);
return false;
}
var match = Regex.Match(versionSansLabel, VersionSansRegEx);
if (!match.Success)
{
result.SetFailure(ParseFailureKind.FormatException);
return false;
}
if (!int.TryParse(match.Groups["major"].Value, out major))
{
result.SetFailure(ParseFailureKind.FormatException);
return false;
}
if (match.Groups["minor"].Success && !int.TryParse(match.Groups["minor"].Value, out minor))
{
result.SetFailure(ParseFailureKind.FormatException);
return false;
}
if (match.Groups["patch"].Success && !int.TryParse(match.Groups["patch"].Value, out patch))
{
result.SetFailure(ParseFailureKind.FormatException);
return false;
}
if (preLabel != null && !Regex.IsMatch(preLabel, LabelUnitRegEx) ||
(buildLabel != null && !Regex.IsMatch(buildLabel, LabelUnitRegEx)))
{
result.SetFailure(ParseFailureKind.FormatException);
return false;
}
result._parsedVersion = new SemanticVersion(major, minor, patch, preLabel, buildLabel);
return true;
}
///
/// Implement ToString()
///
public override string ToString()
{
if (versionString == null)
{
StringBuilder result = new StringBuilder();
result.Append(Major).Append(Utils.Separators.Dot).Append(Minor).Append(Utils.Separators.Dot).Append(Patch);
if (!string.IsNullOrEmpty(PreReleaseLabel))
{
result.Append("-").Append(PreReleaseLabel);
}
if (!string.IsNullOrEmpty(BuildLabel))
{
result.Append("+").Append(BuildLabel);
}
versionString = result.ToString();
}
return versionString;
}
///
/// Implement Compare.
///
public static int Compare(SemanticVersion versionA, SemanticVersion versionB)
{
if (versionA != null)
{
return versionA.CompareTo(versionB);
}
if (versionB != null)
{
return -1;
}
return 0;
}
///
/// Implement
///
public int CompareTo(object version)
{
if (version == null)
{
return 1;
}
var v = version as SemanticVersion;
if (v == null)
{
throw PSTraceSource.NewArgumentException(nameof(version));
}
return CompareTo(v);
}
///
/// Implement .
/// Meets SymVer 2.0 p.11 http://semver.org/
///
public int CompareTo(SemanticVersion value)
{
if ((object)value == null)
return 1;
if (Major != value.Major)
return Major > value.Major ? 1 : -1;
if (Minor != value.Minor)
return Minor > value.Minor ? 1 : -1;
if (Patch != value.Patch)
return Patch > value.Patch ? 1 : -1;
// SymVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata).
return ComparePreLabel(this.PreReleaseLabel, value.PreReleaseLabel);
}
///
/// Override
///
public override bool Equals(object obj)
{
return Equals(obj as SemanticVersion);
}
///
/// Implement
///
public bool Equals(SemanticVersion other)
{
// SymVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata).
return other != null &&
(Major == other.Major) && (Minor == other.Minor) && (Patch == other.Patch) &&
string.Equals(PreReleaseLabel, other.PreReleaseLabel, StringComparison.Ordinal);
}
///
/// Override
///
public override int GetHashCode()
{
return this.ToString().GetHashCode();
}
///
/// Overloaded == operator
///
public static bool operator ==(SemanticVersion v1, SemanticVersion v2)
{
if (object.ReferenceEquals(v1, null))
{
return object.ReferenceEquals(v2, null);
}
return v1.Equals(v2);
}
///
/// Overloaded != operator
///
public static bool operator !=(SemanticVersion v1, SemanticVersion v2)
{
return !(v1 == v2);
}
///
/// Overloaded < operator
///
public static bool operator <(SemanticVersion v1, SemanticVersion v2)
{
return (Compare(v1, v2) < 0);
}
///
/// Overloaded <= operator
///
public static bool operator <=(SemanticVersion v1, SemanticVersion v2)
{
return (Compare(v1, v2) <= 0);
}
///
/// Overloaded > operator
///
public static bool operator >(SemanticVersion v1, SemanticVersion v2)
{
return (Compare(v1, v2) > 0);
}
///
/// Overloaded >= operator
///
public static bool operator >=(SemanticVersion v1, SemanticVersion v2)
{
return (Compare(v1, v2) >= 0);
}
private static int ComparePreLabel(string preLabel1, string preLabel2)
{
// Symver 2.0 standard p.9
// Pre-release versions have a lower precedence than the associated normal version.
// Comparing each dot separated identifier from left to right
// until a difference is found as follows:
// identifiers consisting of only digits are compared numerically
// and identifiers with letters or hyphens are compared lexically in ASCII sort order.
// Numeric identifiers always have lower precedence than non-numeric identifiers.
// A larger set of pre-release fields has a higher precedence than a smaller set,
// if all of the preceding identifiers are equal.
if (String.IsNullOrEmpty(preLabel1)) { return String.IsNullOrEmpty(preLabel2) ? 0 : 1; }
if (String.IsNullOrEmpty(preLabel2)) { return -1; }
var units1 = preLabel1.Split('.');
var units2 = preLabel2.Split('.');
var minLength = units1.Length < units2.Length ? units1.Length : units2.Length;
for (int i = 0; i < minLength; i++)
{
var ac = units1[i];
var bc = units2[i];
int number1, number2;
var isNumber1 = Int32.TryParse(ac, out number1);
var isNumber2 = Int32.TryParse(bc, out number2);
if (isNumber1 && isNumber2)
{
if (number1 != number2) { return number1 < number2 ? -1 : 1; }
}
else
{
if (isNumber1) { return -1; }
if (isNumber2) { return 1; }
int result = String.CompareOrdinal(ac, bc);
if (result != 0) { return result; }
}
}
return units1.Length.CompareTo(units2.Length);
}
internal enum ParseFailureKind
{
ArgumentException,
ArgumentOutOfRangeException,
FormatException
}
internal struct VersionResult
{
internal SemanticVersion _parsedVersion;
internal ParseFailureKind _failure;
internal string _exceptionArgument;
internal bool _canThrow;
internal void Init(bool canThrow)
{
_canThrow = canThrow;
}
internal void SetFailure(ParseFailureKind failure)
{
SetFailure(failure, String.Empty);
}
internal void SetFailure(ParseFailureKind failure, string argument)
{
_failure = failure;
_exceptionArgument = argument;
if (_canThrow)
{
throw GetVersionParseException();
}
}
internal Exception GetVersionParseException()
{
switch (_failure)
{
case ParseFailureKind.ArgumentException:
return PSTraceSource.NewArgumentException("version");
case ParseFailureKind.ArgumentOutOfRangeException:
throw new ValidationMetadataException("ValidateRangeTooSmall",
null, Metadata.ValidateRangeSmallerThanMinRangeFailure,
_exceptionArgument, "0");
case ParseFailureKind.FormatException:
// Regenerate the FormatException as would be thrown by Int32.Parse()
try
{
Int32.Parse(_exceptionArgument, CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
return e;
}
catch (OverflowException e)
{
return e;
}
break;
}
return PSTraceSource.NewArgumentException("version");
}
}
}
}