Skip to content

Commit 64b77fb

Browse files
authored
Merge pull request #1194 from rjmholt/compat-aliases
Fix compatibility profile query API so that aliases referring to other modules appear
2 parents 9a557de + 23e1051 commit 64b77fb

File tree

3 files changed

+143
-46
lines changed

3 files changed

+143
-46
lines changed

PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Query/Modules/ModuleData.cs

+35-32
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,30 @@ namespace Microsoft.PowerShell.CrossCompatibility.Query
1313
/// </summary>
1414
public class ModuleData
1515
{
16+
private readonly RuntimeData _parent;
17+
1618
private readonly Data.ModuleData _moduleData;
1719

18-
private readonly Lazy<Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>, IReadOnlyDictionary<string, CommandData>>> _commands;
20+
private readonly Lazy<Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>>> _lazyCommands;
21+
22+
private readonly Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>> _lazyAliases;
1923

2024
/// <summary>
2125
/// Create a query object around a module data object.
2226
/// </summary>
2327
/// <param name="name">The name of the module.</param>
2428
/// <param name="version">The version of the module.</param>
2529
/// <param name="moduleData">The module data object.</param>
26-
public ModuleData(string name, Version version, Data.ModuleData moduleData)
30+
public ModuleData(string name, Version version, RuntimeData parent, Data.ModuleData moduleData)
2731
{
2832
_moduleData = moduleData;
33+
_parent = parent;
2934

3035
Name = name;
3136
Version = version;
3237

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

3642
/// <summary>
@@ -51,12 +57,12 @@ public ModuleData(string name, Version version, Data.ModuleData moduleData)
5157
/// <summary>
5258
/// Functions exported by the module.
5359
/// </summary>
54-
public IReadOnlyDictionary<string, FunctionData> Functions => _commands.Value.Item1;
60+
public IReadOnlyDictionary<string, FunctionData> Functions => _lazyCommands.Value.Item1;
5561

5662
/// <summary>
5763
/// Cmdlets exported by the module.
5864
/// </summary>
59-
public IReadOnlyDictionary<string, CmdletData> Cmdlets => _commands.Value.Item2;
65+
public IReadOnlyDictionary<string, CmdletData> Cmdlets => _lazyCommands.Value.Item2;
6066

6167
/// <summary>
6268
/// Variables exported by the module.
@@ -66,16 +72,33 @@ public ModuleData(string name, Version version, Data.ModuleData moduleData)
6672
/// <summary>
6773
/// Aliases exported by the module.
6874
/// </summary>
69-
public IReadOnlyDictionary<string, CommandData> Aliases => _commands.Value.Item3;
75+
public IReadOnlyDictionary<string, IReadOnlyList<CommandData>> Aliases => _lazyAliases.Value;
76+
77+
private IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateAliasTable(IReadOnlyDictionary<string, string> aliases)
78+
{
79+
if (aliases == null || aliases.Count == 0)
80+
{
81+
return null;
82+
}
83+
84+
var aliasTable = new Dictionary<string, IReadOnlyList<CommandData>>(StringComparer.OrdinalIgnoreCase);
85+
foreach (KeyValuePair<string, string> alias in aliases)
86+
{
87+
if (_parent.Aliases.TryGetValue(alias.Key, out IReadOnlyList<CommandData> aliasedCommands))
88+
{
89+
aliasTable[alias.Key] = aliasedCommands;
90+
}
91+
}
7092

71-
private static Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>, IReadOnlyDictionary<string, CommandData>> CreateCommandTables(
93+
return aliasTable;
94+
}
95+
96+
private static Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>> CreateCommandTables(
7297
IReadOnlyDictionary<string, Data.FunctionData> functions,
73-
IReadOnlyDictionary<string, Data.CmdletData> cmdlets,
74-
IReadOnlyDictionary<string, string> aliases)
98+
IReadOnlyDictionary<string, Data.CmdletData> cmdlets)
7599
{
76100
Dictionary<string, FunctionData> funcDict = null;
77101
Dictionary<string, CmdletData> cmdletDict = null;
78-
Dictionary<string, CommandData> aliasDict = null;
79102

80103
if (functions != null)
81104
{
@@ -95,29 +118,9 @@ private static Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDiction
95118
}
96119
}
97120

98-
if (aliases != null)
99-
{
100-
aliasDict = new Dictionary<string, CommandData>(aliases.Count, StringComparer.OrdinalIgnoreCase);
101-
foreach (KeyValuePair<string, string> alias in aliases)
102-
{
103-
if (funcDict != null && funcDict.TryGetValue(alias.Value, out FunctionData function))
104-
{
105-
aliasDict[alias.Key] = function;
106-
continue;
107-
}
108-
109-
if (cmdletDict != null && cmdletDict.TryGetValue(alias.Value, out CmdletData cmdlet))
110-
{
111-
aliasDict[alias.Key] = cmdlet;
112-
continue;
113-
}
114-
}
115-
}
116-
117-
return new Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>, IReadOnlyDictionary<string, CommandData>>(
121+
return new Tuple<IReadOnlyDictionary<string, FunctionData>, IReadOnlyDictionary<string, CmdletData>>(
118122
funcDict,
119-
cmdletDict,
120-
aliasDict);
123+
cmdletDict);
121124
}
122125
}
123126
}

PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Query/RuntimeData.cs

+96-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Licensed under the MIT License.
33

44
using System;
5+
using System.Collections;
56
using System.Collections.Generic;
7+
using System.Linq;
68
using Data = Microsoft.PowerShell.CrossCompatibility.Data;
79

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

19+
private readonly Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>> _nonAliasCommands;
20+
21+
private readonly Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>> _aliases;
22+
1723
private readonly Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>> _commands;
1824

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

3036
_modules = new Lazy<IReadOnlyDictionary<string, IReadOnlyDictionary<Version, ModuleData>>>(() => CreateModuleTable(runtimeData.Modules));
31-
_commands = new Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>>(() => CreateCommandLookupTable(Modules));
37+
_nonAliasCommands = new Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>>(() => CreateNonAliasCommandLookupTable(Modules));
38+
_aliases = new Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>>(() => CreateAliasLookupTable(runtimeData.Modules, NonAliasCommands));
39+
_commands = new Lazy<IReadOnlyDictionary<string, IReadOnlyList<CommandData>>>(() => new DualLookupTable<string, IReadOnlyList<CommandData>>(NonAliasCommands, Aliases));
3240
_nativeCommands = new Lazy<NativeCommandLookupTable>(() => NativeCommandLookupTable.Create(runtimeData.NativeCommands));
3341
}
3442

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

61-
private static IReadOnlyDictionary<string, IReadOnlyDictionary<Version, ModuleData>> CreateModuleTable(IDictionary<string, JsonDictionary<Version, Data.ModuleData>> modules)
69+
internal IReadOnlyDictionary<string, IReadOnlyList<CommandData>> NonAliasCommands => _nonAliasCommands.Value;
70+
71+
internal IReadOnlyDictionary<string, IReadOnlyList<CommandData>> Aliases => _aliases.Value;
72+
73+
private IReadOnlyDictionary<string, IReadOnlyDictionary<Version, ModuleData>> CreateModuleTable(IDictionary<string, JsonDictionary<Version, Data.ModuleData>> modules)
6274
{
6375
var moduleDict = new Dictionary<string, IReadOnlyDictionary<Version, ModuleData>>(modules.Count, StringComparer.OrdinalIgnoreCase);
6476
foreach (KeyValuePair<string, JsonDictionary<Version, Data.ModuleData>> moduleVersions in modules)
6577
{
6678
var moduleVersionDict = new Dictionary<Version, ModuleData>(moduleVersions.Value.Count);
6779
foreach (KeyValuePair<Version, Data.ModuleData> module in moduleVersions.Value)
6880
{
69-
moduleVersionDict[module.Key] = new ModuleData(name: moduleVersions.Key, version: module.Key, moduleData: module.Value);
81+
moduleVersionDict[module.Key] = new ModuleData(name: moduleVersions.Key, version: module.Key, parent: this, moduleData: module.Value);
7082
}
7183
moduleDict[moduleVersions.Key] = moduleVersionDict;
7284
}
7385
return moduleDict;
7486
}
7587

76-
private static IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateCommandLookupTable(
88+
private static IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateNonAliasCommandLookupTable(
7789
IReadOnlyDictionary<string, IReadOnlyDictionary<Version, ModuleData>> modules)
7890
{
7991
var commandTable = new Dictionary<string, IReadOnlyList<CommandData>>(StringComparer.OrdinalIgnoreCase);
@@ -106,23 +118,94 @@ private static IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateCom
106118
((List<CommandData>)commandTable[function.Key]).Add(function.Value);
107119
}
108120
}
121+
}
122+
}
109123

110-
if (module.Aliases != null)
124+
return commandTable;
125+
}
126+
127+
private static IReadOnlyDictionary<string, IReadOnlyList<CommandData>> CreateAliasLookupTable(
128+
IReadOnlyDictionary<string, JsonDictionary<Version, Data.ModuleData>> modules,
129+
IReadOnlyDictionary<string, IReadOnlyList<CommandData>> commands)
130+
{
131+
var aliasTable = new Dictionary<string, IReadOnlyList<CommandData>>();
132+
foreach (KeyValuePair<string, JsonDictionary<Version, Data.ModuleData>> module in modules)
133+
{
134+
foreach (KeyValuePair<Version, Data.ModuleData> moduleVersion in module.Value)
135+
{
136+
if (moduleVersion.Value.Aliases == null)
111137
{
112-
foreach (KeyValuePair<string, CommandData> alias in module.Aliases)
113-
{
114-
if (!commandTable.ContainsKey(alias.Key))
115-
{
116-
commandTable.Add(alias.Key, new List<CommandData>());
117-
}
138+
continue;
139+
}
118140

119-
((List<CommandData>)commandTable[alias.Key]).Add(alias.Value);
141+
foreach (KeyValuePair<string, string> alias in moduleVersion.Value.Aliases)
142+
{
143+
if (commands.TryGetValue(alias.Value, out IReadOnlyList<CommandData> aliasedCommands))
144+
{
145+
aliasTable[alias.Key] = aliasedCommands;
120146
}
121147
}
122148
}
123149
}
150+
return aliasTable;
151+
}
124152

125-
return commandTable;
153+
private class DualLookupTable<K, V> : IReadOnlyDictionary<K, V>
154+
{
155+
private readonly IReadOnlyDictionary<K, V> _firstTable;
156+
157+
private readonly IReadOnlyDictionary<K, V> _secondTable;
158+
159+
public DualLookupTable(IReadOnlyDictionary<K, V> firstTable, IReadOnlyDictionary<K, V> secondTable)
160+
{
161+
_firstTable = firstTable;
162+
_secondTable = secondTable;
163+
}
164+
165+
public V this[K key]
166+
{
167+
get
168+
{
169+
if (_firstTable.TryGetValue(key, out V firstValue))
170+
{
171+
return firstValue;
172+
}
173+
174+
if (_secondTable.TryGetValue(key, out V secondValue))
175+
{
176+
return secondValue;
177+
}
178+
179+
throw new KeyNotFoundException();
180+
}
181+
}
182+
183+
public IEnumerable<K> Keys => _firstTable.Keys.Concat(_secondTable.Keys);
184+
185+
public IEnumerable<V> Values => _firstTable.Values.Concat(_secondTable.Values);
186+
187+
public int Count => _firstTable.Count + _secondTable.Count;
188+
189+
public bool ContainsKey(K key)
190+
{
191+
return _firstTable.ContainsKey(key) || _secondTable.ContainsKey(key);
192+
}
193+
194+
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
195+
{
196+
return _firstTable.Concat(_secondTable).GetEnumerator();
197+
}
198+
199+
public bool TryGetValue(K key, out V value)
200+
{
201+
return _firstTable.TryGetValue(key, out value)
202+
|| _secondTable.TryGetValue(key, out value);
203+
}
204+
205+
IEnumerator IEnumerable.GetEnumerator()
206+
{
207+
return GetEnumerator();
208+
}
126209
}
127210
}
128211
}

Tests/Rules/UseCompatibleCommands.Tests.ps1

+12-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ $script:CompatibilityTestCases = @(
3636
@{ Target = $script:Srv2012_3_profile; Script = 'Get-FileHash $pshome\powershell.exe | Format-List'; Commands = @("Get-FileHash"); Version = "3.0"; OS = "Windows"; ProblemCount = 1 }
3737
@{ Target = $script:Srv2012_3_profile; Script = 'Get-ChildItem ./ | Format-List'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
3838
@{ Target = $script:Srv2012_3_profile; Script = 'Save-Help -Module $m -DestinationPath "C:\SavedHelp"'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
39+
@{ Target = $script:Srv2012_3_profile; Script = 'gci .'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
40+
@{ Target = $script:Srv2012_3_profile; Script = 'iex $expr | % { Transform $_ }'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
3941

4042
@{ Target = $script:Srv2012r2_4_profile; Script = 'Write-Information "Information"'; Commands = @("Write-Information"); Version = "4.0"; OS = "Windows"; ProblemCount = 1 }
4143
@{ Target = $script:Srv2012r2_4_profile; Script = '"Hello World" | ConvertFrom-String | Get-Member'; Commands = @("ConvertFrom-String"); Version = "4.0"; OS = "Windows"; ProblemCount = 1 }
@@ -51,11 +53,16 @@ $script:CompatibilityTestCases = @(
5153
@{ Target = $script:Srv2012r2_4_profile; Script = 'Start-Job { Write-Host "Hello" } | Debug-Job'; Commands = @("Debug-Job"); Version = "4.0"; OS = "Windows"; ProblemCount = 1 }
5254
@{ 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 }
5355
@{ Target = $script:Srv2012r2_4_profile; Script = 'Get-ChildItem ./ | Format-List'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
56+
@{ Target = $script:Srv2012r2_4_profile; Script = 'gci .'; Commands = @(); Version = "4.0"; OS = "Windows"; ProblemCount = 0 }
57+
@{ Target = $script:Srv2012r2_4_profile; Script = 'iex $expr | % { Transform $_ }'; Commands = @(); Version = "4.0"; OS = "Windows"; ProblemCount = 0 }
5458

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

6067
@{ Target = $script:Srv2019_6_1_profile; Script = "Add-PSSnapIn MySnapIn"; Commands = @("Add-PSSnapIn"); Version = "6.1"; OS = "Windows"; ProblemCount = 1 }
6168
@{ Target = $script:Srv2019_6_1_profile; Script = 'ConvertFrom-String $str'; Commands = @("ConvertFrom-String"); Version = "6.1"; OS = "Windows"; ProblemCount = 1 }
@@ -88,13 +95,17 @@ $script:CompatibilityTestCases = @(
8895
@{ 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 }
8996
@{ Target = $script:Srv2019_6_1_profile; Script = 'curl $uri'; Commands = @("curl"); Version = "6.1"; OS = "Windows"; ProblemCount = 1 }
9097
@{ Target = $script:Srv2019_6_1_profile; Script = 'Get-ChildItem ./ | Format-List'; Commands = @(); Version = "3.0"; OS = "Windows"; ProblemCount = 0 }
98+
@{ Target = $script:Srv2019_6_1_profile; Script = 'gci .'; Commands = @(); Version = "6.1"; OS = "Windows"; ProblemCount = 0 }
99+
@{ Target = $script:Srv2016_6_1_profile; Script = 'iex $expr | % { Transform $_ }'; Commands = @(); Version = "6.1"; OS = "Windows"; ProblemCount = 0 }
91100

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

100111
$script:ParameterCompatibilityTestCases = @(

0 commit comments

Comments
 (0)