Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
PSVersionInfo.cs1039 linesDownload Raw Back to engine
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Globalization;6using System.Text;7using System.Text.RegularExpressions;8 9using Microsoft.Win32;10 11namespace System.Management.Automation12{13    /// <summary>14    /// <para>15    /// Encapsulates $PSVersionTable.16    /// </para>17    /// <para>18    /// Provides a simple interface to retrieve details from the PowerShell version table:19    /// <code>20    ///    PSVersionInfo.PSVersion;21    /// </code>22    /// The above statement retrieves the PowerShell version.23    /// <code>24    ///    PSVersionInfo.PSEdition;25    /// </code>26    /// The above statement retrieves the PowerShell edition.27    /// </para>28    /// </summary>29    public static partial class PSVersionInfo30    {31        internal const string PSVersionTableName = "PSVersionTable";32        internal const string PSRemotingProtocolVersionName = "PSRemotingProtocolVersion";33        internal const string PSVersionName = "PSVersion";34        internal const string PSEditionName = "PSEdition";35        internal const string PSGitCommitIdName = "GitCommitId";36        internal const string PSCompatibleVersionsName = "PSCompatibleVersions";37        internal const string PSPlatformName = "Platform";38        internal const string PSOSName = "OS";39        internal const string SerializationVersionName = "SerializationVersion";40        internal const string WSManStackVersionName = "WSManStackVersion";41 42        private static readonly PSVersionHashTable s_psVersionTable;43 44        /*45            The following constants are generated by the source generator 'PSVersionInfoGenerator':46 47                internal const string ProductVersion;48                internal const string GitCommitId;49 50                private const int Version_Major51                private const int Version_Minor;52                private const int Version_Patch;53                private const string Version_Label;54        */55 56        /// <summary>57        /// A constant to track current PowerShell Version.58        /// </summary>59        /// <remarks>60        /// We can't depend on assembly version for PowerShell version.61        ///62        /// This is why we hard code the PowerShell version here.63        ///64        /// For each later release of PowerShell, this constant needs to65        /// be updated to reflect the right version.66        /// </remarks>67        private static readonly Version s_psV1Version = new(1, 0);68        private static readonly Version s_psV2Version = new(2, 0);69        private static readonly Version s_psV3Version = new(3, 0);70        private static readonly Version s_psV4Version = new(4, 0);71        private static readonly Version s_psV5Version = new(5, 0);72        private static readonly Version s_psV51Version = new(5, 1);73        private static readonly Version s_psV6Version = new(6, 0);74        private static readonly Version s_psV7Version = new(7, 0);75        private static readonly Version s_psVersion;76        private static readonly SemanticVersion s_psSemVersion;77 78        /// <summary>79        /// A constant to track current PowerShell Edition.80        /// </summary>81        internal const string PSEditionValue = "Core";82 83        // Static Constructor.84        static PSVersionInfo()85        {86            s_psVersionTable = new PSVersionHashTable(StringComparer.OrdinalIgnoreCase);87 88            s_psSemVersion = Version_Label == string.Empty89                ? new SemanticVersion(Version_Major, Version_Minor, Version_Patch)90                : new SemanticVersion(Version_Major, Version_Minor, Version_Patch, Version_Label, buildLabel: null);91            s_psVersion = (Version)s_psSemVersion;92 93            s_psVersionTable[PSVersionName] = s_psSemVersion;94            s_psVersionTable[PSEditionName] = PSEditionValue;95            s_psVersionTable[PSGitCommitIdName] = GitCommitId;96            s_psVersionTable[PSCompatibleVersionsName] = new Version[] { s_psV1Version, s_psV2Version, s_psV3Version, s_psV4Version, s_psV5Version, s_psV51Version, s_psV6Version, s_psV7Version };97            s_psVersionTable[SerializationVersionName] = new Version(InternalSerializer.DefaultVersion);98            s_psVersionTable[PSRemotingProtocolVersionName] = RemotingConstants.ProtocolVersion;99            s_psVersionTable[WSManStackVersionName] = GetWSManStackVersion();100            s_psVersionTable[PSPlatformName] = Environment.OSVersion.Platform.ToString();101            s_psVersionTable[PSOSName] = Runtime.InteropServices.RuntimeInformation.OSDescription;102        }103 104        internal static PSVersionHashTable GetPSVersionTable()105        {106            return s_psVersionTable;107        }108 109        internal static Hashtable GetPSVersionTableForDownLevel()110        {111            var result = (Hashtable)s_psVersionTable.Clone();112            // Downlevel systems don't support SemanticVersion, but Version is most likely good enough anyway.113            result[PSVersionInfo.PSVersionName] = s_psVersion;114            return result;115        }116 117        #region Private helper methods118 119        // Gets the current WSMan stack version from the registry.120        private static Version GetWSManStackVersion()121        {122            Version version = null;123 124#if !UNIX125            try126            {127                using (RegistryKey wsManStackVersionKey = Registry.LocalMachine.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\WSMAN"))128                {129                    if (wsManStackVersionKey != null)130                    {131                        object wsManStackVersionObj = wsManStackVersionKey.GetValue("ServiceStackVersion");132                        string wsManStackVersion = (wsManStackVersionObj != null) ? (string)wsManStackVersionObj : null;133                        if (!string.IsNullOrEmpty(wsManStackVersion))134                        {135                            version = new Version(wsManStackVersion.Trim());136                        }137                    }138                }139            }140            catch (ObjectDisposedException) { }141            catch (System.Security.SecurityException) { }142            catch (ArgumentException) { }143            catch (System.IO.IOException) { }144            catch (UnauthorizedAccessException) { }145            catch (FormatException) { }146            catch (OverflowException) { }147            catch (InvalidCastException) { }148#endif149 150            return version ?? System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STACK_VERSION;151        }152 153        #endregion154 155        #region Programmer APIs156 157        /// <summary>158        /// Gets the version of PowerShell.159        /// </summary>160        public static Version PSVersion161        {162            get163            {164                return s_psVersion;165            }166        }167 168        /// <summary>169        /// Gets the edition of PowerShell.170        /// </summary>171        public static string PSEdition172        {173            get174            {175                return PSEditionValue;176            }177        }178 179        internal static Version SerializationVersion180        {181            get182            {183                return (Version)s_psVersionTable[SerializationVersionName];184            }185        }186 187        /// <summary>188        /// </summary>189        /// <remarks>190        /// For 3.0 PowerShell, we use "3" as the registry version key only for Engine191        /// related data like ApplicationBase.192        /// For 3.0 PowerShell, we still use "1" as the registry version key for193        /// Snapin and Custom shell lookup/discovery.194        /// </remarks>195        internal static string RegistryVersionKey196        {197            get198            {199                // PowerShell >=4 is compatible with PowerShell 3 and hence reg key is 3.200                return "3";201            }202        }203 204        internal static string GetRegistryVersionKeyForSnapinDiscovery(string majorVersion)205        {206            int tempMajorVersion = 0;207            LanguagePrimitives.TryConvertTo<int>(majorVersion, out tempMajorVersion);208 209            if ((tempMajorVersion >= 1) && (tempMajorVersion <= PSVersionInfo.PSVersion.Major))210            {211                // PowerShell version 3 took a dependency on CLR4 and went with:212                // SxS approach in GAC/Registry and in-place upgrade approach for213                // FileSystem.214                // For >=3.0 PowerShell, we still use "1" as the registry version key for215                // Snapin and Custom shell lookup/discovery.216                return "1";217            }218 219            return null;220        }221 222        internal static bool IsValidPSVersion(Version version)223        {224            if (version is null)225            {226                return false;227            }228 229            int minor = version.Minor;230            switch (version.Major)231            {232                case 1:233                case 2:234                case 3:235                case 4:236                    return minor == 0;237                case 5:238                    return minor == 0 || minor == 1;239                case 6:240                    return minor >= 0 && minor <= 2;241                case 7:242                    return minor >= 0 && minor <= s_psVersion.Minor;243            }244 245            return false;246        }247 248        internal static SemanticVersion PSCurrentVersion249        {250            get { return s_psSemVersion; }251        }252 253        #endregion254    }255 256    /// <summary>257    /// Represents an implementation of '$PSVersionTable' variable.258    /// The implementation contains ordered 'Keys' and 'GetEnumerator' to get user-friendly output.259    /// </summary>260    public sealed class PSVersionHashTable : Hashtable, IEnumerable261    {262        private static readonly PSVersionTableComparer s_keysComparer = new PSVersionTableComparer();263 264        internal PSVersionHashTable(IEqualityComparer equalityComparer) : base(equalityComparer)265        {266        }267 268        /// <summary>269        /// Returns ordered collection with Keys of 'PSVersionHashTable'270        /// We want see special order:271        ///     1. PSVersionName272        ///     2. PSEditionName273        ///     3. Remaining properties in alphabetical order.274        /// </summary>275        public override ICollection Keys276        {277            get278            {279                ArrayList keyList = new ArrayList(base.Keys);280                keyList.Sort(s_keysComparer);281                return keyList;282            }283        }284 285        private sealed class PSVersionTableComparer : IComparer286        {287            public int Compare(object x, object y)288            {289                string xString = (string)LanguagePrimitives.ConvertTo(x, typeof(string), CultureInfo.CurrentCulture);290                string yString = (string)LanguagePrimitives.ConvertTo(y, typeof(string), CultureInfo.CurrentCulture);291                if (PSVersionInfo.PSVersionName.Equals(xString, StringComparison.OrdinalIgnoreCase))292                {293                    return -1;294                }295                else if (PSVersionInfo.PSVersionName.Equals(yString, StringComparison.OrdinalIgnoreCase))296                {297                    return 1;298                }299                else if (PSVersionInfo.PSEditionName.Equals(xString, StringComparison.OrdinalIgnoreCase))300                {301                    return -1;302                }303                else if (PSVersionInfo.PSEditionName.Equals(yString, StringComparison.OrdinalIgnoreCase))304                {305                    return 1;306                }307                else308                {309                    return string.Compare(xString, yString, StringComparison.OrdinalIgnoreCase);310                }311            }312        }313 314        /// <summary>315        /// Returns an enumerator for 'PSVersionHashTable'.316        /// The enumeration is ordered (based on ordered version of 'Keys').317        /// </summary>318        IEnumerator IEnumerable.GetEnumerator()319        {320            foreach (object key in Keys)321            {322                yield return new DictionaryEntry(key, this[key]);323            }324        }325    }326 327    /// <summary>328    /// An implementation of semantic versioning (https://semver.org)329    /// that can be converted to/from <see cref="System.Version"/>.330    ///331    /// When converting to <see cref="Version"/>, a PSNoteProperty is332    /// added to the instance to store the semantic version label so333    /// that it can be recovered when creating a new SemanticVersion.334    /// </summary>335    public sealed class SemanticVersion : IComparable, IComparable<SemanticVersion>, IEquatable<SemanticVersion>336    {337        private const string VersionSansRegEx = @"^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?$";338        private const string LabelRegEx = @"^(?<preLabel>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+(?<buildLabel>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$";339        private const string LabelUnitRegEx = @"^((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)$";340        private const string BuildUnitRegEx = @"^([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)$";341        private const string PreLabelPropertyName = "PSSemVerPreReleaseLabel";342        private const string BuildLabelPropertyName = "PSSemVerBuildLabel";343        private const string TypeNameForVersionWithLabel = "System.Version#IncludeLabel";344 345        private string versionString;346 347        /// <summary>348        /// Construct a SemanticVersion from a string.349        /// </summary>350        /// <param name="version">The version to parse.</param>351        /// <exception cref="FormatException"></exception>352        /// <exception cref="OverflowException"></exception>353        public SemanticVersion(string version)354        {355            var v = SemanticVersion.Parse(version);356 357            Major = v.Major;358            Minor = v.Minor;359            Patch = v.Patch < 0 ? 0 : v.Patch;360            PreReleaseLabel = v.PreReleaseLabel;361            BuildLabel = v.BuildLabel;362        }363 364        /// <summary>365        /// Construct a SemanticVersion.366        /// </summary>367        /// <param name="major">The major version.</param>368        /// <param name="minor">The minor version.</param>369        /// <param name="patch">The patch version.</param>370        /// <param name="preReleaseLabel">The pre-release label for the version.</param>371        /// <param name="buildLabel">The build metadata for the version.</param>372        /// <exception cref="FormatException">373        /// If <paramref name="preReleaseLabel"/> don't match 'LabelUnitRegEx'.374        /// If <paramref name="buildLabel"/> don't match 'BuildUnitRegEx'.375        /// </exception>376        public SemanticVersion(int major, int minor, int patch, string preReleaseLabel, string buildLabel)377            : this(major, minor, patch)378        {379            if (!string.IsNullOrEmpty(preReleaseLabel))380            {381                if (!Regex.IsMatch(preReleaseLabel, LabelUnitRegEx))382                {383                    throw new FormatException(nameof(preReleaseLabel));384                }385 386                PreReleaseLabel = preReleaseLabel;387            }388 389            if (!string.IsNullOrEmpty(buildLabel))390            {391                if (!Regex.IsMatch(buildLabel, BuildUnitRegEx))392                {393                    throw new FormatException(nameof(buildLabel));394                }395 396                BuildLabel = buildLabel;397            }398        }399 400        /// <summary>401        /// Construct a SemanticVersion.402        /// </summary>403        /// <param name="major">The major version.</param>404        /// <param name="minor">The minor version.</param>405        /// <param name="patch">The minor version.</param>406        /// <param name="label">The label for the version.</param>407        /// <exception cref="PSArgumentException">408        /// <exception cref="FormatException">409        /// If <paramref name="label"/> don't match 'LabelRegEx'.410        /// </exception>411        public SemanticVersion(int major, int minor, int patch, string label)412            : this(major, minor, patch)413        {414            // We presume the SemVer :415            // 1) major.minor.patch-label416            // 2) 'label' starts with letter or digit.417            if (!string.IsNullOrEmpty(label))418            {419                var match = Regex.Match(label, LabelRegEx);420                if (!match.Success)421                {422                    throw new FormatException(nameof(label));423                }424 425                PreReleaseLabel = match.Groups["preLabel"].Value;426                BuildLabel = match.Groups["buildLabel"].Value;427            }428        }429 430        /// <summary>431        /// Construct a SemanticVersion.432        /// </summary>433        /// <param name="major">The major version.</param>434        /// <param name="minor">The minor version.</param>435        /// <param name="patch">The minor version.</param>436        /// <exception cref="PSArgumentException">437        /// If <paramref name="major"/>, <paramref name="minor"/>, or <paramref name="patch"/> is less than 0.438        /// </exception>439        public SemanticVersion(int major, int minor, int patch)440        {441            if (major < 0)442            {443                throw PSTraceSource.NewArgumentException(nameof(major));444            }445 446            if (minor < 0)447            {448                throw PSTraceSource.NewArgumentException(nameof(minor));449            }450 451            if (patch < 0)452            {453                throw PSTraceSource.NewArgumentException(nameof(patch));454            }455 456            Major = major;457            Minor = minor;458            Patch = patch;459            // We presume:460            // PreReleaseLabel = null;461            // BuildLabel = null;462        }463 464        /// <summary>465        /// Construct a SemanticVersion.466        /// </summary>467        /// <param name="major">The major version.</param>468        /// <param name="minor">The minor version.</param>469        /// <exception cref="PSArgumentException">470        /// If <paramref name="major"/> or <paramref name="minor"/> is less than 0.471        /// </exception>472        public SemanticVersion(int major, int minor) : this(major, minor, 0) { }473 474        /// <summary>475        /// Construct a SemanticVersion.476        /// </summary>477        /// <param name="major">The major version.</param>478        /// <exception cref="PSArgumentException">479        /// If <paramref name="major"/> is less than 0.480        /// </exception>481        public SemanticVersion(int major) : this(major, 0, 0) { }482 483        /// <summary>484        /// Construct a <see cref="SemanticVersion"/> from a <see cref="Version"/>,485        /// copying the NoteProperty storing the label if the expected property exists.486        /// </summary>487        /// <param name="version">The version.</param>488        /// <exception cref="ArgumentNullException">489        /// If <paramref name="version"/> is null.490        /// </exception>491        /// <exception cref="PSArgumentException">492        /// If <paramref name="version.Revision"/> is more than 0.493        /// </exception>494        public SemanticVersion(Version version)495        {496            if (version == null)497            {498                throw PSTraceSource.NewArgumentNullException(nameof(version));499            }500 501            if (version.Revision > 0)502            {503                throw PSTraceSource.NewArgumentException(nameof(version));504            }505 506            Major = version.Major;507            Minor = version.Minor;508            Patch = version.Build == -1 ? 0 : version.Build;509            var psobj = new PSObject(version);510            var preLabelNote = psobj.Properties[PreLabelPropertyName];511            if (preLabelNote != null)512            {513                PreReleaseLabel = preLabelNote.Value as string;514            }515 516            var buildLabelNote = psobj.Properties[BuildLabelPropertyName];517            if (buildLabelNote != null)518            {519                BuildLabel = buildLabelNote.Value as string;520            }521        }522 523        /// <summary>524        /// Convert a <see cref="SemanticVersion"/> to a <see cref="Version"/>.525        /// If there is a <see cref="PreReleaseLabel"/> or/and a <see cref="BuildLabel"/>,526        /// it is added as a NoteProperty to the result so that you can round trip527        /// back to a <see cref="SemanticVersion"/> without losing the label.528        /// </summary>529        /// <param name="semver"></param>530        public static implicit operator Version(SemanticVersion semver)531        {532            PSObject psobj;533 534            var result = new Version(semver.Major, semver.Minor, semver.Patch);535 536            if (!string.IsNullOrEmpty(semver.PreReleaseLabel) || !string.IsNullOrEmpty(semver.BuildLabel))537            {538                psobj = new PSObject(result);539 540                if (!string.IsNullOrEmpty(semver.PreReleaseLabel))541                {542                    psobj.Properties.Add(new PSNoteProperty(PreLabelPropertyName, semver.PreReleaseLabel));543                }544 545                if (!string.IsNullOrEmpty(semver.BuildLabel))546                {547                    psobj.Properties.Add(new PSNoteProperty(BuildLabelPropertyName, semver.BuildLabel));548                }549 550                psobj.TypeNames.Insert(0, TypeNameForVersionWithLabel);551            }552 553            return result;554        }555 556        /// <summary>557        /// The major version number, never negative.558        /// </summary>559        public int Major { get; }560 561        /// <summary>562        /// The minor version number, never negative.563        /// </summary>564        public int Minor { get; }565 566        /// <summary>567        /// The patch version, -1 if not specified.568        /// </summary>569        public int Patch { get; }570 571        /// <summary>572        /// PreReleaseLabel position in the SemVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'.573        /// </summary>574        public string PreReleaseLabel { get; }575 576        /// <summary>577        /// BuildLabel position in the SemVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'.578        /// </summary>579        public string BuildLabel { get; }580 581        /// <summary>582        /// Parse <paramref name="version"/> and return the result if it is a valid <see cref="SemanticVersion"/>, otherwise throws an exception.583        /// </summary>584        /// <param name="version">The string to parse.</param>585        /// <returns></returns>586        /// <exception cref="PSArgumentException"></exception>587        /// <exception cref="FormatException"></exception>588        /// <exception cref="OverflowException"></exception>589        public static SemanticVersion Parse(string version)590        {591            if (version == null)592            {593                throw PSTraceSource.NewArgumentNullException(nameof(version));594            }595 596            if (version == string.Empty)597            {598                throw new FormatException(nameof(version));599            }600 601            var r = new VersionResult();602            r.Init(true);603            TryParseVersion(version, ref r);604 605            return r._parsedVersion;606        }607 608        /// <summary>609        /// Parse <paramref name="version"/> and return true if it is a valid <see cref="SemanticVersion"/>, otherwise return false.610        /// No exceptions are raised.611        /// </summary>612        /// <param name="version">The string to parse.</param>613        /// <param name="result">The return value when the string is a valid <see cref="SemanticVersion"/></param>614        public static bool TryParse(string version, out SemanticVersion result)615        {616            if (version != null)617            {618                var r = new VersionResult();619                r.Init(false);620 621                if (TryParseVersion(version, ref r))622                {623                    result = r._parsedVersion;624                    return true;625                }626            }627 628            result = null;629            return false;630        }631 632        private static bool TryParseVersion(string version, ref VersionResult result)633        {634            if (version.EndsWith('-') || version.EndsWith('+') || version.EndsWith('.'))635            {636                result.SetFailure(ParseFailureKind.FormatException);637                return false;638            }639 640            string versionSansLabel = null;641            var major = 0;642            var minor = 0;643            var patch = 0;644            string preLabel = null;645            string buildLabel = null;646 647            // We parse the SemVer 'version' string 'major.minor.patch-PreReleaseLabel+BuildLabel'.648            var dashIndex = version.IndexOf('-');649            var plusIndex = version.IndexOf('+');650 651            if (dashIndex > plusIndex)652            {653                // 'PreReleaseLabel' can contains dashes.654                if (plusIndex == -1)655                {656                    // No buildLabel: buildLabel == null657                    // Format is 'major.minor.patch-PreReleaseLabel'658                    preLabel = version.Substring(dashIndex + 1);659                    versionSansLabel = version.Substring(0, dashIndex);660                }661                else662                {663                    // No PreReleaseLabel: preLabel == null664                    // Format is 'major.minor.patch+BuildLabel'665                    buildLabel = version.Substring(plusIndex + 1);666                    versionSansLabel = version.Substring(0, plusIndex);667                    dashIndex = -1;668                }669            }670            else671            {672                if (plusIndex == -1)673                {674                    // Here dashIndex == plusIndex == -1675                    // No preLabel - preLabel == null;676                    // No buildLabel - buildLabel == null;677                    // Format is 'major.minor.patch'678                    versionSansLabel = version;679                }680                else if (dashIndex == -1)681                {682                    // No PreReleaseLabel: preLabel == null683                    // Format is 'major.minor.patch+BuildLabel'684                    buildLabel = version.Substring(plusIndex + 1);685                    versionSansLabel = version.Substring(0, plusIndex);686                }687                else688                {689                    // Format is 'major.minor.patch-PreReleaseLabel+BuildLabel'690                    preLabel = version.Substring(dashIndex + 1, plusIndex - dashIndex - 1);691                    buildLabel = version.Substring(plusIndex + 1);692                    versionSansLabel = version.Substring(0, dashIndex);693                }694            }695 696            if ((dashIndex != -1 && string.IsNullOrEmpty(preLabel)) ||697                (plusIndex != -1 && string.IsNullOrEmpty(buildLabel)) ||698                string.IsNullOrEmpty(versionSansLabel))699            {700                // We have dash and no preReleaseLabel  or701                // we have plus and no buildLabel or702                // we have no main version part (versionSansLabel==null)703                result.SetFailure(ParseFailureKind.FormatException);704                return false;705            }706 707            var match = Regex.Match(versionSansLabel, VersionSansRegEx);708            if (!match.Success)709            {710                result.SetFailure(ParseFailureKind.FormatException);711                return false;712            }713 714            if (!int.TryParse(match.Groups["major"].Value, out major))715            {716                result.SetFailure(ParseFailureKind.FormatException);717                return false;718            }719 720            if (match.Groups["minor"].Success && !int.TryParse(match.Groups["minor"].Value, out minor))721            {722                result.SetFailure(ParseFailureKind.FormatException);723                return false;724            }725 726            if (match.Groups["patch"].Success && !int.TryParse(match.Groups["patch"].Value, out patch))727            {728                result.SetFailure(ParseFailureKind.FormatException);729                return false;730            }731 732            if (preLabel != null && !Regex.IsMatch(preLabel, LabelUnitRegEx) ||733               (buildLabel != null && !Regex.IsMatch(buildLabel, BuildUnitRegEx)))734            {735                result.SetFailure(ParseFailureKind.FormatException);736                return false;737            }738 739            result._parsedVersion = new SemanticVersion(major, minor, patch, preLabel, buildLabel);740            return true;741        }742 743        /// <summary>744        /// Implement ToString()745        /// </summary>746        public override string ToString()747        {748            if (versionString == null)749            {750                StringBuilder result = new StringBuilder();751 752                result.Append(Major).Append('.').Append(Minor).Append('.').Append(Patch);753 754                if (!string.IsNullOrEmpty(PreReleaseLabel))755                {756                    result.Append('-').Append(PreReleaseLabel);757                }758 759                if (!string.IsNullOrEmpty(BuildLabel))760                {761                    result.Append('+').Append(BuildLabel);762                }763 764                versionString = result.ToString();765            }766 767            return versionString;768        }769 770        /// <summary>771        /// Implement Compare.772        /// </summary>773        public static int Compare(SemanticVersion versionA, SemanticVersion versionB)774        {775            if (versionA != null)776            {777                return versionA.CompareTo(versionB);778            }779 780            if (versionB != null)781            {782                return -1;783            }784 785            return 0;786        }787 788        /// <summary>789        /// Implement <see cref="IComparable.CompareTo"/>790        /// </summary>791        public int CompareTo(object version)792        {793            if (version == null)794            {795                return 1;796            }797 798            if (version is not SemanticVersion v)799            {800                throw PSTraceSource.NewArgumentException(nameof(version));801            }802 803            return CompareTo(v);804        }805 806        /// <summary>807        /// Implement <see cref="IComparable{T}.CompareTo"/>.808        /// Meets SemVer 2.0 p.11 https://semver.org/809        /// </summary>810        public int CompareTo(SemanticVersion value)811        {812            if (value is null)813                return 1;814 815            if (Major != value.Major)816                return Major > value.Major ? 1 : -1;817 818            if (Minor != value.Minor)819                return Minor > value.Minor ? 1 : -1;820 821            if (Patch != value.Patch)822                return Patch > value.Patch ? 1 : -1;823 824            // SemVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata).825            return ComparePreLabel(this.PreReleaseLabel, value.PreReleaseLabel);826        }827 828        /// <summary>829        /// Override <see cref="object.Equals(object)"/>830        /// </summary>831        public override bool Equals(object obj)832        {833            return Equals(obj as SemanticVersion);834        }835 836        /// <summary>837        /// Implement <see cref="IEquatable{T}.Equals(T)"/>838        /// </summary>839        public bool Equals(SemanticVersion other)840        {841            // SemVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata).842            return other != null &&843                   (Major == other.Major) && (Minor == other.Minor) && (Patch == other.Patch) &&844                   string.Equals(PreReleaseLabel, other.PreReleaseLabel, StringComparison.Ordinal);845        }846 847        /// <summary>848        /// Override <see cref="object.GetHashCode()"/>849        /// </summary>850        public override int GetHashCode()851        {852            return this.ToString().GetHashCode();853        }854 855        /// <summary>856        /// Overloaded == operator.857        /// </summary>858        public static bool operator ==(SemanticVersion v1, SemanticVersion v2)859        {860            if (v1 is null)861            {862                return v2 is null;863            }864 865            return v1.Equals(v2);866        }867 868        /// <summary>869        /// Overloaded != operator.870        /// </summary>871        public static bool operator !=(SemanticVersion v1, SemanticVersion v2)872        {873            return !(v1 == v2);874        }875 876        /// <summary>877        /// Overloaded &lt; operator.878        /// </summary>879        public static bool operator <(SemanticVersion v1, SemanticVersion v2)880        {881            return (Compare(v1, v2) < 0);882        }883 884        /// <summary>885        /// Overloaded &lt;= operator.886        /// </summary>887        public static bool operator <=(SemanticVersion v1, SemanticVersion v2)888        {889            return (Compare(v1, v2) <= 0);890        }891 892        /// <summary>893        /// Overloaded &gt; operator.894        /// </summary>895        public static bool operator >(SemanticVersion v1, SemanticVersion v2)896        {897            return (Compare(v1, v2) > 0);898        }899 900        /// <summary>901        /// Overloaded &gt;= operator.902        /// </summary>903        public static bool operator >=(SemanticVersion v1, SemanticVersion v2)904        {905            return (Compare(v1, v2) >= 0);906        }907 908        private static int ComparePreLabel(string preLabel1, string preLabel2)909        {910            // SemVer 2.0 standard p.9911            // Pre-release versions have a lower precedence than the associated normal version.912            // Comparing each dot separated identifier from left to right913            // until a difference is found as follows:914            //     identifiers consisting of only digits are compared numerically915            //     and identifiers with letters or hyphens are compared lexically in ASCII sort order.916            // Numeric identifiers always have lower precedence than non-numeric identifiers.917            // A larger set of pre-release fields has a higher precedence than a smaller set,918            // if all of the preceding identifiers are equal.919            if (string.IsNullOrEmpty(preLabel1))920            {921                return string.IsNullOrEmpty(preLabel2) ? 0 : 1;922            }923 924            if (string.IsNullOrEmpty(preLabel2))925            {926                return -1;927            }928 929            var units1 = preLabel1.Split('.');930            var units2 = preLabel2.Split('.');931 932            var minLength = units1.Length < units2.Length ? units1.Length : units2.Length;933 934            for (int i = 0; i < minLength; i++)935            {936                var ac = units1[i];937                var bc = units2[i];938                int number1, number2;939                var isNumber1 = Int32.TryParse(ac, out number1);940                var isNumber2 = Int32.TryParse(bc, out number2);941 942                if (isNumber1 && isNumber2)943                {944                    if (number1 != number2)945                    {946                        return number1 < number2 ? -1 : 1;947                    }948                }949                else950                {951                    if (isNumber1)952                    {953                        return -1;954                    }955 956                    if (isNumber2)957                    {958                        return 1;959                    }960 961                    int result = string.CompareOrdinal(ac, bc);962                    if (result != 0)963                    {964                        return result;965                    }966                }967            }968 969            return units1.Length.CompareTo(units2.Length);970        }971 972        internal enum ParseFailureKind973        {974            ArgumentException,975            ArgumentOutOfRangeException,976            FormatException977        }978 979        internal struct VersionResult980        {981            internal SemanticVersion _parsedVersion;982            internal ParseFailureKind _failure;983            internal string _exceptionArgument;984            internal bool _canThrow;985 986            internal void Init(bool canThrow)987            {988                _canThrow = canThrow;989            }990 991            internal void SetFailure(ParseFailureKind failure)992            {993                SetFailure(failure, string.Empty);994            }995 996            internal void SetFailure(ParseFailureKind failure, string argument)997            {998                _failure = failure;999                _exceptionArgument = argument;1000                if (_canThrow)1001                {1002                    throw GetVersionParseException();1003                }1004            }1005 1006            internal Exception GetVersionParseException()1007            {1008                switch (_failure)1009                {1010                    case ParseFailureKind.ArgumentException:1011                        return PSTraceSource.NewArgumentException("version");1012                    case ParseFailureKind.ArgumentOutOfRangeException:1013                        throw new ValidationMetadataException("ValidateRangeTooSmall",1014                            null, Metadata.ValidateRangeSmallerThanMinRangeFailure,1015                            _exceptionArgument, "0");1016                    case ParseFailureKind.FormatException:1017                        // Regenerate the FormatException as would be thrown by Int32.Parse()1018                        try1019                        {1020                            Int32.Parse(_exceptionArgument, CultureInfo.InvariantCulture);1021                        }1022                        catch (FormatException e)1023                        {1024                            return e;1025                        }1026                        catch (OverflowException e)1027                        {1028                            return e;1029                        }1030 1031                        break;1032                }1033 1034                return PSTraceSource.NewArgumentException("version");1035            }1036        }1037    }1038}1039