Skip to content

Fix compatibility profile query API so that aliases referring to other modules appear #1194

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

Merged
merged 4 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -13,24 +13,30 @@ namespace Microsoft.PowerShell.CrossCompatibility.Query
/// </summary>
public class ModuleData
{
private readonly RuntimeData _parent;

private readonly Data.ModuleData _moduleData;

private readonly Lazy<Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>, IReadOnlyDictionary<string, CommandData>>> _commands;
private readonly Lazy<Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>>> _lazyCommands;

private readonly Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>> _lazyAliases;

/// <summary>
/// Create a query object around a module data object.
/// </summary>
/// <param name="name">The name of the module.</param>
/// <param name="version">The version of the module.</param>
/// <param name="moduleData">The module data object.</param>
public ModuleData(string name, Version version, Data.ModuleData moduleData)
public ModuleData(string name, Version version, RuntimeData parent, Data.ModuleData moduleData)
{
_moduleData = moduleData;
_parent = parent;

Name = name;
Version = version;

_commands = new Lazy<Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>, IReadOnlyDictionary<string, CommandData>>>(() => CreateCommandTables(moduleData.Functions, moduleData.Cmdlets, moduleData.Aliases));
_lazyCommands = new Lazy<Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>>>(() => CreateCommandTables(moduleData.Functions, moduleData.Cmdlets));
_lazyAliases = new Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>>(() => CreateAliasTable(_moduleData.Aliases));
}

/// <summary>
Expand All @@ -51,12 +57,12 @@ public ModuleData(string name, Version version, Data.ModuleData moduleData)
/// <summary>
/// Functions exported by the module.
/// </summary>
public IReadOnlyDictionary<string, FunctionData> Functions => _commands.Value.Item1;
public IReadOnlyDictionary<string, FunctionData> Functions => _lazyCommands.Value.Item1;

/// <summary>
/// Cmdlets exported by the module.
/// </summary>
public IReadOnlyDictionary<string, CmdletData> Cmdlets => _commands.Value.Item2;
public IReadOnlyDictionary<string, CmdletData> Cmdlets => _lazyCommands.Value.Item2;

/// <summary>
/// Variables exported by the module.
Expand All @@ -66,16 +72,33 @@ public ModuleData(string name, Version version, Data.ModuleData moduleData)
/// <summary>
/// Aliases exported by the module.
/// </summary>
public IReadOnlyDictionary<string, CommandData> Aliases => _commands.Value.Item3;
public IReadOnlyDictionary<string, IReadOnlyList<CommandData>> Aliases => _lazyAliases.Value;

private IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateAliasTable(IReadOnlyDictionary<string, string> aliases)
{
if (aliases == null || aliases.Count == 0)
{
return null;
}

var aliasTable = new Dictionary<string, IReadOnlyList<CommandData>>(StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, string> alias in aliases)
{
if (_parent.Aliases.TryGetValue(alias.Key, out IReadOnlyList<CommandData> aliasedCommands))
{
aliasTable[alias.Key] = aliasedCommands;
}
}

private static Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>, IReadOnlyDictionary<string, CommandData>> CreateCommandTables(
return aliasTable;
}

private static Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>> CreateCommandTables(
IReadOnlyDictionary<string, Data.FunctionData> functions,
IReadOnlyDictionary<string, Data.CmdletData> cmdlets,
IReadOnlyDictionary<string, string> aliases)
IReadOnlyDictionary<string, Data.CmdletData> cmdlets)
{
Dictionary<string, FunctionData> funcDict = null;
Dictionary<string, CmdletData> cmdletDict = null;
Dictionary<string, CommandData> aliasDict = null;

if (functions != null)
{
Expand All @@ -95,29 +118,9 @@ private static Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDiction
}
}

if (aliases != null)
{
aliasDict = new Dictionary<string, CommandData>(aliases.Count, StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, string> alias in aliases)
{
if (funcDict != null && funcDict.TryGetValue(alias.Value, out FunctionData function))
{
aliasDict[alias.Key] = function;
continue;
}

if (cmdletDict != null && cmdletDict.TryGetValue(alias.Value, out CmdletData cmdlet))
{
aliasDict[alias.Key] = cmdlet;
continue;
}
}
}

return new Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>, IReadOnlyDictionary<string, CommandData>>(
return new Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>>(
funcDict,
cmdletDict,
aliasDict);
cmdletDict);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Data = Microsoft.PowerShell.CrossCompatibility.Data;

namespace Microsoft.PowerShell.CrossCompatibility.Query
Expand All @@ -14,6 +16,10 @@ public class RuntimeData
{
private readonly Lazy<IReadOnlyDictionary<string, IReadOnlyDictionary<Version, ModuleData>>> _modules;

private readonly Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>> _nonAliasCommands;

private readonly Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>> _aliases;

private readonly Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>> _commands;

private readonly Lazy<NativeCommandLookupTable> _nativeCommands;
Expand All @@ -28,7 +34,9 @@ public RuntimeData(Data.RuntimeData runtimeData)
Common = new CommonPowerShellData(runtimeData.Common);

_modules = new Lazy<IReadOnlyDictionary<string, IReadOnlyDictionary<Version, ModuleData>>>(() => CreateModuleTable(runtimeData.Modules));
_commands = new Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>>(() => CreateCommandLookupTable(Modules));
_nonAliasCommands = new Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>>(() => CreateNonAliasCommandLookupTable(Modules));
_aliases = new Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>>(() => CreateAliasLookupTable(runtimeData.Modules, NonAliasCommands));
_commands = new Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>>(() => new DualLookupTable<string, IReadOnlyList<CommandData>>(NonAliasCommands, Aliases));
_nativeCommands = new Lazy<NativeCommandLookupTable>(() => NativeCommandLookupTable.Create(runtimeData.NativeCommands));
}

Expand Down Expand Up @@ -58,22 +66,26 @@ public RuntimeData(Data.RuntimeData runtimeData)
/// </summary>
public CommonPowerShellData Common { get; }

private static IReadOnlyDictionary<string, IReadOnlyDictionary<Version, ModuleData>> CreateModuleTable(IDictionary<string, JsonDictionary<Version, Data.ModuleData>> modules)
internal IReadOnlyDictionary<string, IReadOnlyList<CommandData>> NonAliasCommands => _nonAliasCommands.Value;

internal IReadOnlyDictionary<string, IReadOnlyList<CommandData>> Aliases => _aliases.Value;

private IReadOnlyDictionary<string, IReadOnlyDictionary<Version, ModuleData>> CreateModuleTable(IDictionary<string, JsonDictionary<Version, Data.ModuleData>> modules)
{
var moduleDict = new Dictionary<string, IReadOnlyDictionary<Version, ModuleData>>(modules.Count, StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, JsonDictionary<Version, Data.ModuleData>> moduleVersions in modules)
{
var moduleVersionDict = new Dictionary<Version, ModuleData>(moduleVersions.Value.Count);
foreach (KeyValuePair<Version, Data.ModuleData> module in moduleVersions.Value)
{
moduleVersionDict[module.Key] = new ModuleData(name: moduleVersions.Key, version: module.Key, moduleData: module.Value);
moduleVersionDict[module.Key] = new ModuleData(name: moduleVersions.Key, version: module.Key, parent: this, moduleData: module.Value);
}
moduleDict[moduleVersions.Key] = moduleVersionDict;
}
return moduleDict;
}

private static IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateCommandLookupTable(
private static IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateNonAliasCommandLookupTable(
IReadOnlyDictionary<string, IReadOnlyDictionary<Version, ModuleData>> modules)
{
var commandTable = new Dictionary<string, IReadOnlyList<CommandData>>(StringComparer.OrdinalIgnoreCase);
Expand Down Expand Up @@ -106,23 +118,94 @@ private static IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateCom
((List<CommandData>)commandTable[function.Key]).Add(function.Value);
}
}
}
}

if (module.Aliases != null)
return commandTable;
}

private static IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateAliasLookupTable(
IReadOnlyDictionary<string, JsonDictionary<Version, Data.ModuleData>> modules,
IReadOnlyDictionary<string, IReadOnlyList<CommandData>> commands)
{
var aliasTable = new Dictionary<string, IReadOnlyList<CommandData>>();
foreach (KeyValuePair<string, JsonDictionary<Version, Data.ModuleData>> module in modules)
{
foreach (KeyValuePair<Version, Data.ModuleData> moduleVersion in module.Value)
{
if (moduleVersion.Value.Aliases == null)
{
foreach (KeyValuePair<string, CommandData> alias in module.Aliases)
{
if (!commandTable.ContainsKey(alias.Key))
{
commandTable.Add(alias.Key, new List<CommandData>());
}
continue;
}

((List<CommandData>)commandTable[alias.Key]).Add(alias.Value);
foreach (KeyValuePair<string, string> alias in moduleVersion.Value.Aliases)
{
if (commands.TryGetValue(alias.Value, out IReadOnlyList<CommandData> aliasedCommands))
{
aliasTable[alias.Key] = aliasedCommands;
}
}
}
}
return aliasTable;
}

return commandTable;
private class DualLookupTable<K, V> : IReadOnlyDictionary<K, V>
{
private readonly IReadOnlyDictionary<K, V> _firstTable;

private readonly IReadOnlyDictionary<K, V> _secondTable;

public DualLookupTable(IReadOnlyDictionary<K, V> firstTable, IReadOnlyDictionary<K, V> secondTable)
{
_firstTable = firstTable;
_secondTable = secondTable;
}

public V this[K key]
{
get
{
if (_firstTable.TryGetValue(key, out V firstValue))
{
return firstValue;
}

if (_secondTable.TryGetValue(key, out V secondValue))
{
return secondValue;
}

throw new KeyNotFoundException();
}
}

public IEnumerable<K> Keys => _firstTable.Keys.Concat(_secondTable.Keys);

public IEnumerable<V> Values => _firstTable.Values.Concat(_secondTable.Values);

public int Count => _firstTable.Count + _secondTable.Count;

public bool ContainsKey(K key)
{
return _firstTable.ContainsKey(key) || _secondTable.ContainsKey(key);
}

public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
return _firstTable.Concat(_secondTable).GetEnumerator();
}

public bool TryGetValue(K key, out V value)
{
return _firstTable.TryGetValue(key, out value)
|| _secondTable.TryGetValue(key, out value);
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}
13 changes: 12 additions & 1 deletion Tests/Rules/UseCompatibleCommands.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ $script:CompatibilityTestCases = @(
@{ Target = $script:Srv2012_3_profile; Script = 'Get-FileHash $pshome\powershell.exe | Format-List'; Commands = @("Get-FileHash"); Version = "3.0"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2012_3_profile; Script = 'Get-ChildItem ./ | Format-List'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2012_3_profile; Script = 'Save-Help -Module $m -DestinationPath "C:\SavedHelp"'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2012_3_profile; Script = 'gci .'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2012_3_profile; Script = 'iex $expr | % { Transform $_ }'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }

@{ Target = $script:Srv2012r2_4_profile; Script = 'Write-Information "Information"'; Commands = @("Write-Information"); Version = "4.0"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2012r2_4_profile; Script = '"Hello World" | ConvertFrom-String | Get-Member'; Commands = @("ConvertFrom-String"); Version = "4.0"; OS = "Windows"; ProblemCount = 1 }
Expand All @@ -51,11 +53,16 @@ $script:CompatibilityTestCases = @(
@{ Target = $script:Srv2012r2_4_profile; Script = 'Start-Job { Write-Host "Hello" } | Debug-Job'; Commands = @("Debug-Job"); Version = "4.0"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2012r2_4_profile; Script = 'Get-ItemPropertyValue -Path HKLM:\SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine -Name ApplicationBase'; Commands = @("Get-ItemPropertyValue"); Version = "4.0"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2012r2_4_profile; Script = 'Get-ChildItem ./ | Format-List'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2012r2_4_profile; Script = 'gci .'; Commands = @(); Version = "4.0"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2012r2_4_profile; Script = 'iex $expr | % { Transform $_ }'; Commands = @(); Version = "4.0"; OS = "Windows"; ProblemCount = 0 }

@{ Target = $script:Srv2019_5_profile; Script = "Remove-Alias gcm"; Commands = @("Remove-Alias"); Version = "5.1"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2019_5_profile; Script = "Get-Uptime"; Commands = @("Get-Uptime"); Version = "5.1"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2019_5_profile; Script = "Remove-Service 'MyService'"; Commands = @("Remove-Service"); Version = "5.1"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2019_5_profile; Script = 'Get-ChildItem ./ | Format-List'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2019_5_profile; Script = 'gci .'; Commands = @(); Version = "5.1"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2019_5_profile; Script = 'iex $expr | % { Transform $_ }'; Commands = @(); Version = "5.1"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2019_5_profile; Script = 'fhx $filePath'; Commands = @(); Version = "5.1"; OS = "Windows"; ProblemCount = 0 }

@{ Target = $script:Srv2019_6_1_profile; Script = "Add-PSSnapIn MySnapIn"; Commands = @("Add-PSSnapIn"); Version = "6.1"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2019_6_1_profile; Script = 'ConvertFrom-String $str'; Commands = @("ConvertFrom-String"); Version = "6.1"; OS = "Windows"; ProblemCount = 1 }
Expand Down Expand Up @@ -88,13 +95,17 @@ $script:CompatibilityTestCases = @(
@{ Target = $script:Srv2019_6_1_profile; Script = '$zip = New-WebServiceProxy -Uri "http://www.webservicex.net/uszip.asmx?WSDL"'; Commands = @("New-WebServiceProxy"); Version = "6.1"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2019_6_1_profile; Script = 'curl $uri'; Commands = @("curl"); Version = "6.1"; OS = "Windows"; ProblemCount = 1 }
@{ Target = $script:Srv2019_6_1_profile; Script = 'Get-ChildItem ./ | Format-List'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2019_6_1_profile; Script = 'gci .'; Commands = @(); Version = "6.1"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Srv2016_6_1_profile; Script = 'iex $expr | % { Transform $_ }'; Commands = @(); Version = "6.1"; OS = "Windows"; ProblemCount = 0 }

@{ Target = $script:Ubuntu1804_6_1_profile; Script = 'Get-AuthenticodeSignature ./script.ps1'; Commands = @("Get-AuthenticodeSignature"); Version = "6.1"; OS = "Linux"; ProblemCount = 1 }
@{ Target = $script:Ubuntu1804_6_1_profile; Script = 'Get-Service systemd'; Commands = @("Get-Service"); Version = "6.1"; OS = "Linux"; ProblemCount = 1 }
@{ Target = $script:Ubuntu1804_6_1_profile; Script = 'Start-Service -Name "sshd"'; Commands = @("Start-Service"); Version = "6.1"; OS = "Linux"; ProblemCount = 1 }
@{ Target = $script:Ubuntu1804_6_1_profile; Script = 'Get-PSSessionConfiguration -Name Full | Format-List -Property *'; Commands = @("Get-PSSessionConfiguration"); Version = "6.1"; OS = "Linux"; ProblemCount = 1 }
@{ Target = $script:Ubuntu1804_6_1_profile; Script = 'Get-CimInstance Win32_StartupCommand'; Commands = @("Get-CimInstance"); Version = "6.1"; OS = "Linux"; ProblemCount = 1 }
@{ Target = $script:Ubuntu1804_6_1_profile; Script = 'Get-ChildItem ./ | Format-List'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
@{ Target = $script:Ubuntu1804_6_1_profile; Script = 'Get-ChildItem ./ | Format-List'; Commands = @(); Version = "6.1"; OS = "Linux"; ProblemCount = 0 }
@{ Target = $script:Ubuntu1804_6_1_profile; Script = 'gci .'; Commands = @(); Version = "6.1"; OS = "Linux"; ProblemCount = 0 }
@{ Target = $script:Ubuntu1804_6_1_profile; Script = 'iex $expr | % { Transform $_ }'; Commands = @(); Version = "6.1"; OS = "Linux"; ProblemCount = 0 }
)

$script:ParameterCompatibilityTestCases = @(
Expand Down