/********************************************************************++
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 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 SerializationVersionName = "SerializationVersion";
internal const string WSManStackVersionName = "WSManStackVersion";
private static Hashtable 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 relase 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 = new SemanticVersion(6, 0, 0, "alpha");
///
/// A constant to track current PowerShell Edition
///
#if !CORECLR
internal const string PSEditionValue = "Desktop";
#else
internal const string PSEditionValue = "Core";
#endif
// Static Constructor.
static PSVersionInfo()
{
s_psVersionTable = new Hashtable(StringComparer.OrdinalIgnoreCase);
s_psVersionTable[PSVersionInfo.PSVersionName] = s_psV6Version;
s_psVersionTable["PSEdition"] = PSEditionValue;
s_psVersionTable["BuildVersion"] = GetBuildVersion();
s_psVersionTable["GitCommitId"] = GetCommitInfo();
s_psVersionTable["PSCompatibleVersions"] = 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();
#if CORECLR
s_psVersionTable["CLRVersion"] = null;
#else
s_psVersionTable["CLRVersion"] = Environment.Version;
#endif
}
internal static Hashtable 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;
}
internal static Version GetBuildVersion()
{
string assemblyPath = typeof(PSVersionInfo).GetTypeInfo().Assembly.Location;
string buildVersion = FileVersionInfo.GetVersionInfo(assemblyPath).FileVersion;
return new Version(buildVersion);
}
// Get the commit id from the powershell.version file. If the powershell.version file doesn't exist, use the string "N/A"
internal static string GetCommitInfo()
{
try
{
string assemblyPath = IO.Path.GetDirectoryName(typeof(PSVersionInfo).GetTypeInfo().Assembly.Location);
return (IO.File.ReadAllLines(IO.Path.Combine(assemblyPath, "powershell.version"))[0]);
}
catch (Exception e)
{
return e.Message;
}
}
#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 Version CLRVersion
{
get
{
return (Version)GetPSVersionTable()["CLRVersion"];
}
}
internal static Version BuildVersion
{
get
{
return (Version)GetPSVersionTable()["BuildVersion"];
}
}
internal static Version[] PSCompatibleVersions
{
get
{
return (Version[])GetPSVersionTable()["PSCompatibleVersions"];
}
}
internal static string PSEdition
{
get
{
return (string)GetPSVersionTable()["PSEdition"];
}
}
internal static Version SerializationVersion
{
get
{
return (Version)GetPSVersionTable()["SerializationVersion"];
}
}
///
///
///
///
/// 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 GetRegisteryVersionKeyForSnapinDiscovery(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
}
///
/// 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
{
///
/// 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;
Label = v.Label;
}
///
/// Construct a SemanticVersion.
///
/// The major version
/// The minor version
/// The minor version
/// The label for the version
///
/// If , , or is less than 0.
///
///
/// If is null or an empty string.
///
public SemanticVersion(int major, int minor, int patch, string label)
: this(major, minor, patch)
{
if (string.IsNullOrEmpty(label)) throw PSTraceSource.NewArgumentNullException(nameof(label));
Label = label;
}
///
/// 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;
Label = null;
}
private const string LabelPropertyName = "PSSemanticVersionLabel";
///
/// Construct a from a ,
/// copying the NoteProperty storing the label if the expected property exists.
///
/// The version.
public SemanticVersion(Version version)
{
if (version.Revision > 0 || version.Build < 0) throw PSTraceSource.NewArgumentException(nameof(version));
Major = version.Major;
Minor = version.Minor;
Patch = version.Build;
var psobj = new PSObject(version);
var labelNote = psobj.Properties[LabelPropertyName];
if (labelNote != null)
{
Label = labelNote.Value as string;
}
}
///
/// Convert a to a .
/// If there is 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)
{
var result = new Version(semver.Major, semver.Minor, semver.Patch);
if (!string.IsNullOrEmpty(semver.Label))
{
var psobj = new PSObject(result);
psobj.Properties.Add(new PSNoteProperty(LabelPropertyName, semver.Label));
}
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; }
///
/// The last component in a SemanticVersion - may be null if not specified.
///
public string Label { 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));
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)
{
var dashIndex = version.IndexOf('-');
// Empty label?
if (dashIndex == version.Length - 1)
{
result.SetFailure(ParseFailureKind.ArgumentException);
return false;
}
var versionSansLabel = (dashIndex < 0) ? version : version.Substring(0, dashIndex);
string[] parsedComponents = versionSansLabel.Split(Utils.Separators.Dot);
if (parsedComponents.Length != 3)
{
result.SetFailure(ParseFailureKind.ArgumentException);
return false;
}
int major, minor, patch;
if (!TryParseComponent(parsedComponents[0], "major", ref result, out major))
{
return false;
}
if (!TryParseComponent(parsedComponents[1], "minor", ref result, out minor))
{
return false;
}
if (!TryParseComponent(parsedComponents[2], "patch", ref result, out patch))
{
return false;
}
result._parsedVersion = dashIndex < 0
? new SemanticVersion(major, minor, patch)
: new SemanticVersion(major, minor, patch, version.Substring(dashIndex + 1));
return true;
}
private static bool TryParseComponent(string component, string componentName, ref VersionResult result, out int parsedComponent)
{
if (!Int32.TryParse(component, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedComponent))
{
result.SetFailure(ParseFailureKind.FormatException, component);
return false;
}
if (parsedComponent < 0)
{
result.SetFailure(ParseFailureKind.ArgumentOutOfRangeException, componentName);
return false;
}
return true;
}
///
/// ToString
///
public override string ToString()
{
if (Patch < 0)
{
return string.IsNullOrEmpty(Label)
? StringUtil.Format("{0}.{1}", Major, Minor)
: StringUtil.Format("{0}.{1}-{2}", Major, Minor, Label);
}
return string.IsNullOrEmpty(Label)
? StringUtil.Format("{0}.{1}.{2}", Major, Minor, Patch)
: StringUtil.Format("{0}.{1}.{2}-{3}", Major, Minor, Patch, Label);
}
///
/// 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
///
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;
if (Label == null)
return value.Label == null ? 0 : 1;
if (value.Label == null)
return -1;
if (!string.Equals(Label, value.Label, StringComparison.Ordinal))
return string.Compare(Label, value.Label, StringComparison.Ordinal);
return 0;
}
///
/// Override
///
public override bool Equals(object obj)
{
return Equals(obj as SemanticVersion);
}
///
/// Implement
///
public bool Equals(SemanticVersion other)
{
return other != null &&
(Major == other.Major) && (Minor == other.Minor) && (Patch == other.Patch) &&
string.Equals(Label, other.Label, StringComparison.Ordinal);
}
///
/// Override
///
public override int GetHashCode()
{
return Utils.CombineHashCodes(
Major.GetHashCode(),
Minor.GetHashCode(),
Patch.GetHashCode(),
Label == null ? 0 : Label.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)
{
if ((object)v1 == null) throw PSTraceSource.NewArgumentException(nameof(v1));
return (v1.CompareTo(v2) < 0);
}
///
/// Overloaded <= operator
///
public static bool operator <=(SemanticVersion v1, SemanticVersion v2)
{
if ((object)v1 == null) throw PSTraceSource.NewArgumentException(nameof(v1));
return (v1.CompareTo(v2) <= 0);
}
///
/// Overloaded > operator
///
public static bool operator >(SemanticVersion v1, SemanticVersion v2)
{
return (v2 < v1);
}
///
/// Overloaded >= operator
///
public static bool operator >=(SemanticVersion v1, SemanticVersion v2)
{
return (v2 <= v1);
}
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");
}
}
}
}