Skip to content

Commit

Permalink
Update compatibility profiles for PowerShell 7 (#1429)
Browse files Browse the repository at this point in the history
* Update compatibility profiles for PS7

* Update tests

* Fix alias collection

* Update profiles

* Update documentation

* Make queryable profile initialization eager

* Add supression IDs for compatibility rules

* Fix NREs and remove Console.WriteLine

* Add supression documentation

* Activate tests in CI

* Remove 6.1 profiles

* Fix type loading

* Update profiles collected with fixed module

* Remove Wait-Debugger

* Fix other tests

* Update tests for PS 3

* Fix PS 4 tests

* Fix types tests

* Fix syntax

* Fix the way parse errors are filtered out in tests

* Correct docs

* Add more specific Win10 name to docs
  • Loading branch information
rjmholt authored Mar 31, 2020
1 parent 2514855 commit b6e0736
Show file tree
Hide file tree
Showing 41 changed files with 542 additions and 334 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation;
using System.Reflection;
using Microsoft.PowerShell.Commands;
using Microsoft.PowerShell.CrossCompatibility.Data;
using Microsoft.PowerShell.CrossCompatibility.Utility;
using SMA = System.Management.Automation;
using System.IO;

#if CoreCLR
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -78,7 +77,7 @@ public CompatibilityProfileCollector Build(SMA.PowerShell pwsh)
pwsh,
platformInfoCollector,
_pwshDataCollectorBuilder.Build(pwsh, platformInfoCollector.PSVersion),
_typeDataColletorBuilder.Build());
_typeDataColletorBuilder.Build(Path.GetDirectoryName(typeof(SMA.PowerShell).Assembly.Location)));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,14 @@ public Tuple<string, Version, ModuleData> GetCoreModuleData()
var moduleData = new ModuleData();

IEnumerable<CommandInfo> coreCommands = _pwsh.AddCommand(GcmInfo)
.AddParameter("Module", CORE_MODULE_NAME)
.InvokeAndClear<CommandInfo>();
.AddParameter("Type", CommandTypes.Alias | CommandTypes.Cmdlet | CommandTypes.Function)
.InvokeAndClear<CommandInfo>()
.Where(commandInfo => string.IsNullOrEmpty(commandInfo.ModuleName) || CORE_MODULE_NAME.Equals(commandInfo.ModuleName, StringComparison.OrdinalIgnoreCase));

var cmdletData = new JsonCaseInsensitiveStringDictionary<CmdletData>();
var functionData = new JsonCaseInsensitiveStringDictionary<FunctionData>();
var aliases = new JsonCaseInsensitiveStringDictionary<string>();
var aliasesToRequest = new List<string>();
foreach (CommandInfo command in coreCommands)
{
switch (command)
Expand Down Expand Up @@ -315,6 +318,24 @@ public Tuple<string, Version, ModuleData> GetCoreModuleData()
}
continue;

case AliasInfo alias:
try
{
// Some aliases won't resolve unless specified specifically
if (alias.Definition == null)
{
aliasesToRequest.Add(alias.Name);
continue;
}

aliases.Add(alias.Name, alias.Definition);
}
catch (RuntimeException)
{
// Ignore aliases that have trouble loading
}
continue;

default:
throw new CompatibilityAnalysisException($"Command {command.Name} in core module is of unsupported type {command.CommandType}");
}
Expand All @@ -323,15 +344,31 @@ public Tuple<string, Version, ModuleData> GetCoreModuleData()
moduleData.Cmdlets = cmdletData;
moduleData.Functions = functionData;

if (aliasesToRequest != null && aliasesToRequest.Count > 0)
{
IEnumerable<AliasInfo> resolvedAliases = _pwsh.AddCommand(GcmInfo)
.AddParameter("Name", aliasesToRequest)
.InvokeAndClear<AliasInfo>();

foreach (AliasInfo resolvedAlias in resolvedAliases)
{
if (resolvedAlias?.Definition == null)
{
continue;
}

aliases[resolvedAlias.Name] = resolvedAlias.Definition;
}
}

// Get default variables and core aliases out of a fresh runspace
using (SMA.PowerShell freshPwsh = SMA.PowerShell.Create(RunspaceMode.NewRunspace))
{
Collection<PSObject> varsAndAliases = freshPwsh.AddCommand("Get-ChildItem")
.AddParameter("Path", "variable:,alias:")
.AddParameter("Path", "variable:")
.InvokeAndClear();

var variables = new List<string>();
var aliases = new JsonCaseInsensitiveStringDictionary<string>();

foreach (PSObject returnedObject in varsAndAliases)
{
Expand All @@ -341,10 +378,6 @@ public Tuple<string, Version, ModuleData> GetCoreModuleData()
variables.Add(variable.Name);
continue;

case AliasInfo alias:
aliases.Add(alias.Name, GetSingleAliasData(alias));
continue;

// Skip over other objects we get back, since there's no reason to throw
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using SMA = System.Management.Automation;
using System.Runtime.InteropServices;
using System.IO;
using Newtonsoft.Json.Serialization;

namespace Microsoft.PowerShell.CrossCompatibility.Collection
{
Expand All @@ -37,9 +38,9 @@ public class Builder
/// Build the configured TypeDataCollector object.
/// </summary>
/// <returns>The constructed TypeDataCollector object.</returns>
public TypeDataCollector Build()
public TypeDataCollector Build(string psHomePath)
{
return new TypeDataCollector(ExcludedAssemblyPathPrefixes);
return new TypeDataCollector(psHomePath, ExcludedAssemblyPathPrefixes);
}
}

Expand All @@ -53,8 +54,13 @@ public TypeDataCollector Build()

private readonly IReadOnlyCollection<string> _excludedAssemblyPathPrefixes;

private TypeDataCollector(IReadOnlyCollection<string> excludedAssemblyPathPrefixes)
private readonly string _psHomePath;

private TypeDataCollector(
string psHomePath,
IReadOnlyCollection<string> excludedAssemblyPathPrefixes)
{
_psHomePath = psHomePath;
_excludedAssemblyPathPrefixes = excludedAssemblyPathPrefixes;
}

Expand All @@ -65,10 +71,49 @@ private TypeDataCollector(IReadOnlyCollection<string> excludedAssemblyPathPrefix
/// <returns>A data object describing the assemblies and PowerShell type accelerators available.</returns>
public AvailableTypeData GetAvailableTypeData(out IEnumerable<CompatibilityAnalysisException> errors)
{
// PS 6+ behaves as if assemblies in PSHOME are already loaded when they aren't
// so we must load them pre-emptively to capture the correct behavior
#if CoreCLR
List<CompatibilityAnalysisException> psHomeLoadErrors = new List<CompatibilityAnalysisException>();
foreach (string dllPath in Directory.GetFiles(_psHomePath))
{
if (!string.Equals(Path.GetExtension(dllPath), ".dll"))
{
continue;
}

try
{
Assembly.LoadFrom(dllPath);
}
catch (Exception e)
{
psHomeLoadErrors.Add(new CompatibilityAnalysisException($"Unable to load PSHOME DLL at path '{dllPath}'", e));
}
}
#endif

IReadOnlyDictionary<string, Type> typeAccelerators = GetTypeAccelerators();
IEnumerable<Assembly> loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();

#if !CoreCLR
return AssembleAvailableTypes(loadedAssemblies, typeAccelerators, out errors);
#else

AvailableTypeData typeData = AssembleAvailableTypes(loadedAssemblies, typeAccelerators, out IEnumerable<CompatibilityAnalysisException> typeCollectionErrors);

if (psHomeLoadErrors.Count > 0)
{
psHomeLoadErrors.AddRange(typeCollectionErrors);
errors = psHomeLoadErrors;
}
else
{
errors = typeCollectionErrors;
}

return typeData;
#endif
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Microsoft.PowerShell.CrossCompatibility.Collection;
using Microsoft.PowerShell.CrossCompatibility.Data;
using Microsoft.PowerShell.CrossCompatibility.Retrieval;
using Microsoft.PowerShell.CrossCompatibility.Utility;
using Newtonsoft.Json;
using SMA = System.Management.Automation;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,30 @@ namespace Microsoft.PowerShell.CrossCompatibility.Query
/// </summary>
public class CommonPowerShellData
{
private readonly Lazy<Tuple<IReadOnlyDictionary<string, ParameterData>, IReadOnlyDictionary<string, ParameterData>>> _parameters;

/// <summary>
/// Create a new query object for common PowerShell data.
/// </summary>
/// <param name="commonPowerShellData">The mutable data object holding common data information.</param>
public CommonPowerShellData(Data.CommonPowerShellData commonPowerShellData)
{
_parameters = new Lazy<Tuple<IReadOnlyDictionary<string, ParameterData>, IReadOnlyDictionary<string, ParameterData>>>(() => CreateParameterTable(commonPowerShellData.Parameters, commonPowerShellData.ParameterAliases));
Parameters = CreateParameterTable(commonPowerShellData.Parameters, commonPowerShellData.ParameterAliases, out IReadOnlyDictionary<string, ParameterData> parameterAliases);
ParameterAliases = parameterAliases;
}

/// <summary>
/// Common parameters, present on all commands bound with the cmdlet binding.
/// </summary>
public IReadOnlyDictionary<string, ParameterData> Parameters => _parameters.Value.Item1;
public IReadOnlyDictionary<string, ParameterData> Parameters { get; }

/// <summary>
/// Aliases for common parameters.
/// </summary>
public IReadOnlyDictionary<string, ParameterData> ParameterAliases => _parameters.Value.Item2;
public IReadOnlyDictionary<string, ParameterData> ParameterAliases { get; }

private Tuple<IReadOnlyDictionary<string, ParameterData>, IReadOnlyDictionary<string, ParameterData>> CreateParameterTable(
private IReadOnlyDictionary<string, ParameterData> CreateParameterTable(
IReadOnlyDictionary<string, Data.ParameterData> parameters,
IReadOnlyDictionary<string, string> parameterAliases)
IReadOnlyDictionary<string, string> parameterAliases,
out IReadOnlyDictionary<string, ParameterData> queryAliases)
{
var parameterDict = new Dictionary<string, ParameterData>(parameters.Count + parameterAliases.Count, StringComparer.OrdinalIgnoreCase);
var parameterAliasDict = new Dictionary<string, ParameterData>(parameterAliases.Count, StringComparer.OrdinalIgnoreCase);
Expand All @@ -52,9 +52,8 @@ private Tuple<IReadOnlyDictionary<string, ParameterData>, IReadOnlyDictionary<st
parameterDict[parameterAlias.Key] = aliasedParameter;
}

return new Tuple<IReadOnlyDictionary<string, ParameterData>, IReadOnlyDictionary<string, ParameterData>>(
parameterDict,
parameterAliasDict);
queryAliases = parameterAliasDict;
return parameterDict;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,32 @@ namespace Microsoft.PowerShell.CrossCompatibility.Query
/// </summary>
public abstract class CommandData
{
protected readonly Data.CommandData _commandData;

/// <summary>
/// Create a new command data query object from the data object.
/// </summary>
/// <param name="name">The name of the command.</param>
/// <param name="commandData">The command data object describing the command.</param>
protected CommandData(string name, Data.CommandData commandData)
{
_commandData = commandData;
Name = name;
DefaultParameterSet = commandData.DefaultParameterSet;

if (commandData.OutputType != null)
{
OutputType = new List<string>(commandData.OutputType);
}

if (commandData.ParameterSets != null)
{
ParameterSets = new List<string>(commandData.ParameterSets);
}

var parameters = new Dictionary<string, ParameterData>(StringComparer.OrdinalIgnoreCase);
var paramAliases = new Dictionary<string, ParameterData>(StringComparer.OrdinalIgnoreCase);

if (commandData.Parameters != null)
{
foreach (KeyValuePair<string, Microsoft.PowerShell.CrossCompatibility.Data.ParameterData> parameter in commandData.Parameters)
foreach (KeyValuePair<string, Data.ParameterData> parameter in commandData.Parameters)
{
parameters.Add(parameter.Key, new ParameterData(parameter.Key, parameter.Value));
}
Expand Down Expand Up @@ -59,17 +67,17 @@ protected CommandData(string name, Data.CommandData commandData)
/// <summary>
/// The output types of the command, if any.
/// </summary>
public IReadOnlyList<string> OutputType => _commandData.OutputType;
public IReadOnlyList<string> OutputType { get; }

/// <summary>
/// The parameter sets of the command, if any.
/// </summary>
public IReadOnlyList<string> ParameterSets => _commandData.ParameterSets;
public IReadOnlyList<string> ParameterSets { get; }

/// <summary>
/// The default parameter set of the command, if any.
/// </summary>
public string DefaultParameterSet => _commandData.DefaultParameterSet;
public string DefaultParameterSet { get; }

/// <summary>
/// Parameter aliases of the command.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ public class FunctionData : CommandData
public FunctionData(string name, Data.FunctionData functionData)
: base(name, functionData)
{
IsCmdletBinding = functionData.CmdletBinding;
}

/// <summary>
/// True if this is an advanced function (has a cmdlet binding), false otherwise.
/// </summary>
public override bool IsCmdletBinding => ((Data.FunctionData)_commandData).CmdletBinding;
public override bool IsCmdletBinding { get; }
}
}
Loading

0 comments on commit b6e0736

Please sign in to comment.