MegaBites-AI/Windows-powershell
0372
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 < 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 <= 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 > 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 >= 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 