Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix profile collection on non-Windows, add PS 7 profiles #1260

Merged
merged 5 commits into from
Aug 23, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public CompatibilityProfileCollector Build(SMA.PowerShell pwsh)
}
}

private static readonly Version s_currentProfileSchemaVersion = new Version(1, 1);
private static readonly Version s_currentProfileSchemaVersion = new Version(1, 2);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am guessing the code works in such a way that and old schema (change only in minor version) is backwards compatible, i.e. old profiles still work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a comment explaining this


private readonly PowerShellDataCollector _pwshDataCollector;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Management.Infrastructure;
using Microsoft.PowerShell.CrossCompatibility.Data;
using Microsoft.PowerShell.CrossCompatibility.Utility;
Expand All @@ -22,13 +23,74 @@ namespace Microsoft.PowerShell.CrossCompatibility.Collection
/// </summary>
public class PlatformInformationCollector : IDisposable
{
/// <summary>
/// Collect all release info files into a lookup table in memory.
/// Overrides pre-existing keys if there are duplicates.
/// </summary>
/// <returns>A dictionary with the keys and values of all the release info files on the machine.</returns>
public static IReadOnlyDictionary<string, string> GetLinuxReleaseInfo()
{
var dict = new Dictionary<string, string>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a more descriptive name could be useful? What about creating it in a case insensitive way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more descriptive name for the method? Not sure what to call it other than this, but open to suggestions.

I thought about case-sensitivity, but ultimately this is Linux-specific and there's nothing to stop two keys being added that differ only by case

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, for dict

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Got it :)


foreach (string path in s_releaseInfoPaths)
{
try
{
using (FileStream fileStream = File.OpenRead(path))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DRY:

Suggested change
using (FileStream fileStream = File.OpenRead(path))
using (var fileStream = File.OpenRead(path))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no repetition here; FileStream only occurs once on the line and it's not obvious that File.OpenRead returns that type

using (var reader = new StreamReader(fileStream))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();

if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
{
continue;
}

string[] elements = line.Split('=');
dict[elements[0]] = Dequote(elements[1]);
}
}
}
catch (IOException)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a comment why this could happen and is OK could be helpful.

{
// Do nothing - just continue
}
}

return dict;
}

// Paths on Linux to search for key/value-paired information about the OS.
private static readonly IReadOnlyCollection<string> s_releaseInfoPaths = new string[]
{
"/etc/lsb-release",
"/etc/os-release",
};

private static readonly IReadOnlyList<string> s_distributionIdKeys = new string[]
{
"ID",
"DISTRIB_ID"
};

private static readonly IReadOnlyList<string> s_distributionVersionKeys = new string[]
{
"VERSION_ID",
"DISTRIB_RELEASE"
};

private static readonly IReadOnlyList<string> s_distributionPrettyNameKeys = new string[]
{
"PRETTY_NAME",
"DISTRIB_DESCRIPTION"
};

private static readonly Regex s_macOSNameRegex = new Regex(
@"System Version: (.*?)(\(|$)",
RegexOptions.Multiline | RegexOptions.Compiled);

private readonly Lazy<Hashtable> _lazyPSVersionTable;

private readonly Lazy<PowerShellVersion> _lazyPSVersion;
Expand Down Expand Up @@ -129,16 +191,17 @@ public OperatingSystemData GetOperatingSystemData()
{
var osData = new OperatingSystemData()
{
Description = GetOSDescription(),
Architecture = GetOSArchitecture(),
Family = GetOSFamily(),
Name = GetOSName(),
Platform = GetOSPlatform(),
Version = GetOSVersion(),
};

switch (osData.Family)
{
case OSFamily.Windows:
osData.Name = osData.Description;
if (!string.IsNullOrEmpty(Environment.OSVersion.ServicePack))
{
osData.ServicePack = Environment.OSVersion.ServicePack;
Expand All @@ -147,53 +210,83 @@ public OperatingSystemData GetOperatingSystemData()
break;

case OSFamily.Linux:
IReadOnlyDictionary<string, string> lsbInfo = GetLinuxReleaseInfo();
osData.DistributionId = lsbInfo["DistributionId"];
osData.DistributionVersion = lsbInfo["DistributionVersion"];
osData.DistributionPrettyName = lsbInfo["DistributionPrettyName"];
IReadOnlyDictionary<string, string> releaseInfo = GetLinuxReleaseInfo();

osData.DistributionId = GetEntryFromReleaseInfo(releaseInfo, s_distributionIdKeys);
osData.DistributionVersion = GetEntryFromReleaseInfo(releaseInfo, s_distributionVersionKeys);
osData.DistributionPrettyName = GetEntryFromReleaseInfo(releaseInfo, s_distributionPrettyNameKeys);
osData.Name = osData.DistributionPrettyName;
break;

case OSFamily.MacOS:
osData.Name = GetMacOSName();
break;
}

return osData;
}

/// <summary>
/// Collect all release info files into a lookup table in memory.
/// Overrides pre-existing keys if there are duplicates.
/// </summary>
/// <returns>A dictionary with the keys and values of all the release info files on the machine.</returns>
public IReadOnlyDictionary<string, string> GetLinuxReleaseInfo()
private string GetMacOSName()
{
var dict = new Dictionary<string, string>();

foreach (string path in s_releaseInfoPaths)
try
{
try
using (var spProcess = new Process())
{
using (FileStream fileStream = File.OpenRead(path))
using (var reader = new StreamReader(fileStream))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
spProcess.StartInfo.UseShellExecute = false;
spProcess.StartInfo.RedirectStandardOutput = true;
spProcess.StartInfo.CreateNoWindow = true;
spProcess.StartInfo.FileName = "/usr/sbin/system_profiler";
spProcess.StartInfo.Arguments = "SPSoftwareDataType";

if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
{
continue;
}
spProcess.Start();
spProcess.WaitForExit();

string[] elements = line.Split('=');
dict[elements[0]] = Dequote(elements[1]);
}
}
}
catch (IOException)
{
// Do nothing - just continue
string output = spProcess.StandardOutput.ReadToEnd();
return s_macOSNameRegex.Match(output).Groups[1].Value;
}
}
catch
{
return null;
}
}

return dict;
private string GetOSDescription()
{
#if CoreCLR
// This key was introduced in PowerShell 6
return (string)PSVersionTable["OS"];
#else
if (_lazyWin32OperatingSystemInfo.IsValueCreated)
{
return _lazyWin32OperatingSystemInfo.Value.OSName;
}

return RegistryCurrentVersionInfo.ProductName;
#endif
}

private string GetOSVersion()
{
#if CoreCLR
// On Linux, we want to record the kernel branch, since this can differentiate Azure
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return File.ReadAllText("/proc/sys/kernel/osrelease");
}
#endif
return Environment.OSVersion.Version.ToString();
}

private string GetOSPlatform()
{
#if CoreCLR
if (PSVersion.Major >= 6)
{
return (string)PSVersionTable["Platform"];
}
#endif
return "Win32NT";
}

private OSFamily GetOSFamily()
Expand Down Expand Up @@ -310,46 +403,6 @@ private uint GetWinSkuId()
return (uint)WindowsSku.Undefined;
}

private string GetOSName()
{
#if CoreCLR
// This key was introduced in PowerShell 6
if (PSVersion.Major >= 6)
{
return (string)PSVersionTable["OS"];
}
#endif
if (_lazyWin32OperatingSystemInfo.IsValueCreated)
{
return _lazyWin32OperatingSystemInfo.Value.OSName;
}

return RegistryCurrentVersionInfo.ProductName;
}

private string GetOSVersion()
{
#if CoreCLR
// On Linux, we want to record the kernel branch, since this can differentiate Azure
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return File.ReadAllText("/proc/sys/kernel/osrelease");
}
#endif
return Environment.OSVersion.Version.ToString();
}

private string GetOSPlatform()
{
#if CoreCLR
if (PSVersion.Major >= 6)
{
return (string)PSVersionTable["Platform"];
}
#endif
return "Win32NT";
}

private static CurrentVersionInfo ReadCurrentVersionFromRegistry()
{
using (RegistryKey currentVersion = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"))
Expand All @@ -360,13 +413,18 @@ private static CurrentVersionInfo ReadCurrentVersionFromRegistry()
}
}

[DllImport("kernel32.dll")]
private static extern bool GetProductInfo(
int dwOSMajorVersion,
int dwOSMinorVersion,
int dwSpMajorVersion,
int dwSpMinorVersion,
out uint pdwReturnedProductType);
private static string GetEntryFromReleaseInfo(IReadOnlyDictionary<string, string> releaseInfo, IEnumerable<string> possibleKeys)
{
foreach (string key in possibleKeys)
{
if (releaseInfo.TryGetValue(key, out string entry))
{
return entry;
}
}

return null;
}

private static Win32OSCimInfo GetWin32OperatingSystemInfo()
{
Expand Down Expand Up @@ -443,6 +501,15 @@ private static string Dequote(string s)
return sb.ToString();
}

[DllImport("kernel32.dll")]
private static extern bool GetProductInfo(
int dwOSMajorVersion,
int dwOSMinorVersion,
int dwSpMajorVersion,
int dwSpMinorVersion,
out uint pdwReturnedProductType);


#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ namespace Microsoft.PowerShell.CrossCompatibility.Data
public class OperatingSystemData : ICloneable
{
/// <summary>
/// The name of the operating system as
/// reported by $PSVersionTable.
/// The name of the operating system.
/// </summary>
[DataMember]
public string Name { get; set; }

/// <summary>
/// The description of the operating system as
/// reported by $PSVersionTable.
/// </summary>
[DataMember]
public string Description { get; set; }

/// <summary>
/// The platform as reported by
/// $PSVersionTable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ public OperatingSystemData(OperatingSystemDataMut operatingSystemData)
}

/// <summary>
/// The name of the operating system as reported by $PSVersionTable.OS.
/// The name of the operating system.
/// </summary>
public string Name => _operatingSystemData.Name;

/// <summary>
/// The description of the operating system as reported by $PSVersionTable.OS.
/// </summary>
public string Description => _operatingSystemData.Description;

/// <summary>
/// The name of the platform as reported by $PSVersionTable.Platform.
/// </summary>
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.