Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
2f93ca2
Use fallback CIM API
rjmholt Mar 15, 2019
df10417
Add platform data collection type
rjmholt Mar 17, 2019
9d3c041
Add C# implementations of powershell data collection
rjmholt Mar 21, 2019
7159b8b
Add more .NET pwsh collection APIs
rjmholt Mar 23, 2019
3fcb48d
Add preliminary New-PSCompatibilityProfile cmdlet implementation
rjmholt Mar 24, 2019
85bd373
Add new cmdlets, change JSON settings, add schema version
Mar 27, 2019
528e47d
Add most validation logic
Mar 28, 2019
f7b3628
Update collectors, add assertion cmdlet
rjmholt Mar 28, 2019
8a75c91
Fix files
Mar 28, 2019
ab660e5
Fix enumeration in json cmdlet
Mar 28, 2019
d7c2169
Fix service pack
Mar 28, 2019
9e2d19b
Fix idiocy
Mar 28, 2019
565dce6
Fix generic parameter types being null
Mar 28, 2019
42d4453
Remove MMI, use registry for windows SKU info
rjmholt Mar 29, 2019
c712760
Convert scrap errors to warnings
Mar 29, 2019
28443d4
Fix error -> warning usage
Mar 29, 2019
28bb895
Fix up sku querying
Apr 8, 2019
0399922
Exclude compat module from own profiles
Apr 8, 2019
40943fb
Add copyright
Apr 8, 2019
6bdbc20
Add doc comments
Apr 8, 2019
dd2035d
Add missing docs
Apr 8, 2019
2ee3c5e
Rename PSCompatibilityAnalyzer to PSCompatibilityCollector
Apr 8, 2019
b41f0eb
Change MMI version
Apr 9, 2019
89a7ae1
Fix Get-TypeAccelerators
Apr 9, 2019
ab506af
Update validation cmdlet
Apr 9, 2019
b63a4ef
Improve cmdlet experience, add rudimentary tests
Apr 9, 2019
0d4d150
Add build error notification to build script
Apr 9, 2019
5b7f8bc
Fix NRE in type collection
Apr 9, 2019
b17418b
Don't copy profiles into published module
Apr 9, 2019
b709669
Add AzF profile
Apr 9, 2019
3be3a34
Add AzA profile
Apr 9, 2019
f00269a
Add verbose appveyor debug
Apr 9, 2019
cffe6c9
More
Apr 9, 2019
4137d91
Try again
Apr 9, 2019
7457e04
Undo module path mangling in test
Apr 10, 2019
bd8ea16
Write PSModulePath before failing test
Apr 10, 2019
97c120d
Fix parameter alias
rjmholt Apr 10, 2019
7287b40
add gmo -list display
rjmholt Apr 10, 2019
88a6a1d
Change test implementation to out-of-proc, add more printing
rjmholt Apr 10, 2019
2e8ae31
Fixup test stuff
rjmholt Apr 10, 2019
aba9e40
Fix module reloading where cmdlets are unloaded but types not
rjmholt Apr 10, 2019
470906b
Try more things
rjmholt Apr 10, 2019
fcab671
Attempt to reload and catch
rjmholt Apr 10, 2019
7198d81
Forget try/catch
rjmholt Apr 10, 2019
6235db4
More info
rjmholt Apr 10, 2019
816f0ec
Try removing first
rjmholt Apr 10, 2019
9222b52
Mark tests as pending
rjmholt Apr 10, 2019
a38373e
Remove whitespace from azure functions profile
rjmholt Apr 10, 2019
ff004d3
Use UTF-16LE for psd1
rjmholt Apr 10, 2019
fa38e1d
Change psd1 back to utf8 with bom
rjmholt Apr 10, 2019
1413c4b
Fix pester test skip
rjmholt Apr 10, 2019
a768d76
Add proper versioning to schema
Apr 10, 2019
66f92a7
Fix up namespaces
Apr 10, 2019
758db97
Fix file ending newlines
Apr 10, 2019
6d10fb0
Update schema version of azure profiles
Apr 10, 2019
eaf2ede
Fix namespaces in rules
Apr 11, 2019
a76ac6c
Rename Az profiles
Apr 11, 2019
138737d
Fix type naming namespace change
Apr 15, 2019
110a3f5
Fix syntax in AzF profile
Apr 15, 2019
dced589
Add macOS profile
rjmholt Apr 22, 2019
b700465
Prevent permissions problem reading bins for version
rjmholt Apr 22, 2019
7f57e61
Add simple command tests for AzF profile
rjmholt Apr 24, 2019
c9663ce
Add types tests
rjmholt Apr 24, 2019
2ce9992
Remove old compatibility module dir
rjmholt Apr 24, 2019
cf97e0b
Add rule enable settings
rjmholt Apr 24, 2019
234fae4
Fix grouping logic
rjmholt Apr 24, 2019
808a7eb
Add breaks to switch
rjmholt Apr 24, 2019
e65d011
Make negative group name test more diagnostic
rjmholt Apr 24, 2019
f04009d
Refine empty group test
rjmholt Apr 24, 2019
1c78a6b
Fix other test
rjmholt Apr 24, 2019
9f6ef22
Remove faulty test component
rjmholt Apr 24, 2019
69c483c
Fix encoding of test files
rjmholt Apr 24, 2019
0bbf54f
Address @JamesWTruher's feedback
rjmholt Apr 25, 2019
db6cd7b
Fix ConvertFrom-PSCompatibilityJson for loop
rjmholt Apr 26, 2019
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
Prev Previous commit
Next Next commit
Add preliminary New-PSCompatibilityProfile cmdlet implementation
  • Loading branch information
rjmholt committed Apr 24, 2019
commit 3fcb48d669baed68f4597772094002f4089f8770
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.PowerShell.Commands;
using Microsoft.PowerShell.CrossCompatibility.Data.Modules;
using Microsoft.PowerShell.CrossCompatibility.Utility;
Expand All @@ -18,6 +19,8 @@ public class PowerShellDataCollector : IDisposable

private const string CORE_MODULE_NAME = "Microsoft.PowerShell.Core";

private static readonly Regex s_typeDataRegex = new Regex("Error in TypeData \"([A-Za-z.]+)\"", RegexOptions.Compiled);

private static readonly CmdletInfo s_gmoInfo = new CmdletInfo("Get-Module", typeof(GetModuleCommand));

private static readonly CmdletInfo s_ipmoInfo = new CmdletInfo("Import-Module", typeof(ImportModuleCommand));
Expand Down Expand Up @@ -77,47 +80,78 @@ public IEnumerable<Tuple<string, Version, ModuleData>> GetModulesData(out IEnume
var errs = new List<Exception>();
foreach (PSModuleInfo module in modules)
{
try
Tuple<string, Version, ModuleData> moduleData = LoadAndGetModuleData(module, out Exception error);

if (moduleData == null)
{
PSModuleInfo importedModule = _pwsh.AddCommand(s_ipmoInfo)
.AddParameter("ModuleInfo", module)
.AddParameter("PassThru")
.AddParameter("ErrorAction", "Stop")
.InvokeAndClear<PSModuleInfo>()
.FirstOrDefault();

moduleDatas.Add(GetSingleModuleData(importedModule));

_pwsh.AddCommand(s_rmoInfo)
.AddParameter("ModuleInfo", importedModule)
.InvokeAndClear();
errs.Add(error);
continue;
}
catch (CmdletInvocationException)

moduleDatas.Add(moduleData);
}

errors = errs;
return moduleDatas;
}

public Tuple<string, Version, ModuleData> LoadAndGetModuleData(PSModuleInfo module, out Exception error)
{
try
{
PSModuleInfo importedModule = _pwsh.AddCommand(s_ipmoInfo)
.AddParameter("ModuleInfo", module)
.AddParameter("PassThru")
.AddParameter("ErrorAction", "Stop")
.InvokeAndClear<PSModuleInfo>()
.FirstOrDefault();

Tuple<string, Version, ModuleData> moduleData = GetSingleModuleData(importedModule);

_pwsh.AddCommand(s_rmoInfo)
.AddParameter("ModuleInfo", importedModule)
.InvokeAndClear();

error = null;
return moduleData;
}
catch (RuntimeException e)
{
// A common problem is TypeData being hit with other modules, this overrides that
if (e.ErrorRecord.FullyQualifiedErrorId.Equals("FormatXmlUpdateException,Microsoft.PowerShell.Commands.ImportModuleCommand")
|| e.ErrorRecord.FullyQualifiedErrorId.Equals("ErrorsUpdatingTypes"))
{
// Attempt to load the module in a new runspace instead
try
{
using (SMA.PowerShell fallbackPwsh = SMA.PowerShell.Create(RunspaceMode.NewRunspace))
{
PSModuleInfo importedModule = fallbackPwsh.AddCommand(s_ipmoInfo)
.AddParameter("Name", module.Path)
.AddParameter("PassThru")
.AddParameter("ErrorAction", "Stop")
.InvokeAndClear<PSModuleInfo>()
.FirstOrDefault();

moduleDatas.Add(GetSingleModuleData(importedModule));
}
}
catch (Exception fallbackException)
foreach (string typeDataName in GetTypeDataNamesFromErrorMessage(e.Message))
{
_pwsh.AddCommand("Remove-TypeData")
.AddParameter("TypeName", typeDataName)
.InvokeAndClear();
}
}

// Attempt to load the module in a new runspace instead
try
{
using (SMA.PowerShell fallbackPwsh = SMA.PowerShell.Create(RunspaceMode.NewRunspace))
{
errs.Add(fallbackException);
PSModuleInfo importedModule = fallbackPwsh.AddCommand(s_ipmoInfo)
.AddParameter("Name", module.Path)
.AddParameter("PassThru")
.AddParameter("Force")
.AddParameter("ErrorAction", "Stop")
.InvokeAndClear<PSModuleInfo>()
.FirstOrDefault();

error = null;
return GetSingleModuleData(importedModule);
}
}
catch (Exception fallbackException)
{
error = fallbackException;
return null;
}
}

errors = errs;
return moduleDatas;
}

public Tuple<string, Version, ModuleData> GetSingleModuleData(PSModuleInfo module)
Expand Down Expand Up @@ -191,9 +225,9 @@ public Tuple<string, Version, ModuleData> GetCoreModuleData()
.InvokeAndClear<PSVariable>();

var variableArray = new string[defaultVariables.Count];
for (int i = 0; i < moduleData.Variables.Length; i++)
for (int i = 0; i < variableArray.Length; i++)
{
moduleData.Variables[i] = defaultVariables[i].Name;
variableArray[i] = defaultVariables[i].Name;
}
moduleData.Variables = variableArray;

Expand Down Expand Up @@ -335,9 +369,16 @@ public FunctionData GetSingleFunctionData(FunctionInfo function)
CmdletBinding = function.CmdletBinding
};

functionData.DefaultParameterSet = GetDefaultParameterSet(function.DefaultParameterSet);

functionData.OutputType = GetOutputType(function.OutputType);
try
{
functionData.DefaultParameterSet = GetDefaultParameterSet(function.DefaultParameterSet);
functionData.OutputType = GetOutputType(function.OutputType);
}
catch (RuntimeException)
{
// function.DefaultParameterSet can fail to do type resolution,
// in which case leave functionData.DefaultParameterSet null
}

functionData.ParameterSets = GetParameterSets(function.ParameterSets);

Expand Down Expand Up @@ -471,6 +512,15 @@ private static ReadOnlySet<string> GetPowerShellCommonParameterNames()
return new ReadOnlySet<string>(set, StringComparer.OrdinalIgnoreCase);
}

private static string[] GetTypeDataNamesFromErrorMessage(string errorMessage)
{
var typeDataNames = new List<string>();
foreach (Match match in s_typeDataRegex.Matches(errorMessage))
{
typeDataNames.Add(match.Groups[1].Value);
}
return typeDataNames.ToArray();
}

#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 @@ -94,7 +94,16 @@ public static AvailableTypeData AssembleAvailableTypes(
}

KeyValuePair<string, AssemblyData> asmData = AssembleAssembly(asm);
asms[asmData.Key] = asmData.Value;
try
{
asms.Add(asmData.Key, asmData.Value);
}
catch (ArgumentException e)
{
// We don't have a way in the schema for two assemblies with the same name, so we just keep the first
// This is not really valid and we should update the schema to subkey the version
errAcc.Add(new CompatibilityAnalysisException($"Found duplicate assemblies with name {asmData.Key}. Kept the first one.", e));
}
}
catch (ReflectionTypeLoadException e)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using Microsoft.PowerShell.CrossCompatibility.Collection;
using Microsoft.PowerShell.CrossCompatibility.Data;
using Microsoft.PowerShell.CrossCompatibility.Utility;
using Newtonsoft.Json;
using SMA = System.Management.Automation;

namespace Microsoft.PowerShell.CrossCompatibility.Commands
{
/// <summary>
/// Creates a new PowerShell compatibility profile for the current PowerShell session
/// </summary>
[Cmdlet(VerbsCommon.New, CommandUtilities.ModulePrefix + "Profile", DefaultParameterSetName = "OutFile")]
public class NewPSCompatibilityProfileCommand : PSCmdlet
{
/// <summary>
/// The name of the default profile directory.
/// This directory lives in the module root.
/// </summary>
private const string DEFAULT_PROFILE_DIR_NAME = "profiles";

/// <summary>
/// The path of the profile file to create.
/// </summary>
[Parameter(ParameterSetName = "OutFile")]
public string OutFile { get; set; }

/// <summary>
/// When set, no file is created and the profile object is returned to the pipeline.
/// </summary>
/// <value></value>
[Parameter(ParameterSetName = "PassThru")]
public SwitchParameter PassThru { get; set; }

/// <summary>
/// When set, the profile is serialized with whitespace-formatted JSON.
/// </summary>
[Parameter(ParameterSetName = "OutFile")]
[Parameter(ParameterSetName = "ProfileName")]
public SwitchParameter NoCompress { get; set; }

/// <summary>
/// The ID of the platform to set in the profile.
/// If OutFile and ProfileName are not set, this will also be the filename of the profile.
/// </summary>
/// <value></value>
[Parameter]
public string PlatformId { get; set; }

/// <summary>
/// Sets the name of the profile file to be created in the default profile directory.
/// </summary>
[Parameter(ParameterSetName = "ProfileName")]
public string ProfileName { get; set; }

protected override void EndProcessing()
{
CompatibilityProfileData profile;
IEnumerable<Exception> errors;
using (var pwsh = SMA.PowerShell.Create())
using (var profileCollector = new CompatibilityProfileCollector(pwsh))
{
profile = string.IsNullOrEmpty(PlatformId)
? profileCollector.GetCompatibilityData(out errors)
: profileCollector.GetCompatibilityData(PlatformId, out errors);
}

// Report any problems we hit
foreach (Exception e in errors)
{
WriteError(new ErrorRecord(e, "CompatibilityProfilerError", ErrorCategory.ReadError, null));
}

// If PassThru is set, just pass the object back and we're done
if (PassThru)
{
WriteObject(profile);
return;
}

// Set the default profile path if it was not provided
string outFilePath;
if (string.IsNullOrEmpty(OutFile))
{
string profileName = ProfileName ?? profile.Id;
outFilePath = GetDefaultProfilePath(profileName);
}
else
{
// Normalize the path to the output file we were given
outFilePath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(OutFile);
}

// Create the directory containing the profile
// If it already exists, this call will do nothing
// If it cannot be created or is a non-directory file, an exception will be raised, which we let users handle
Directory.CreateDirectory(Path.GetDirectoryName(outFilePath));

// Create the JSON serializer
JsonProfileSerializer jsonSerializer = NoCompress
? JsonProfileSerializer.Create(Formatting.Indented)
: JsonProfileSerializer.Create(Formatting.None);

// Write the file
var outFile = new FileInfo(outFilePath);
jsonSerializer.SerializeToFile(profile, outFile);

// Return the FileInfo object for the profile
WriteObject(outFile);
}

private string GetDefaultProfilePath(string profileName)
{
string moduleRoot = Path.GetDirectoryName(MyInvocation.MyCommand.Module.Path);
return Path.Combine(moduleRoot, DEFAULT_PROFILE_DIR_NAME, profileName + ".json");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,20 @@ public void SerializeToFile(object data, string filePath)
}
}

/// <summary>
/// Serialize a given .NET object to a given file, using the FileInfo representation of the file.
/// </summary>
/// <param name="data">The data object to serialize to JSON.</param>
/// <param name="file">The file to serialize to.</param>
public void SerializeToFile(object data, FileInfo file)
{
using (var fileStream = file.OpenRead())
using (var streamWriter = new StreamWriter(fileStream, Encoding.UTF8))
{
_serializer.Serialize(streamWriter, data);
}
}

/// <summary>
/// Hydrate a compatibility profile object from a given file.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,26 @@ internal static class PowerShellExtensions
{
public static Collection<PSObject> InvokeAndClear(this SMA.PowerShell pwsh)
{
Collection<PSObject> result = pwsh.Invoke();
pwsh.Commands.Clear();
return result;
try
{
return pwsh.Invoke();
}
finally
{
pwsh.Commands.Clear();
}
}

public static Collection<T> InvokeAndClear<T>(this SMA.PowerShell pwsh)
{
Collection<T> result = pwsh.Invoke<T>();
pwsh.Commands.Clear();
return result;
try
{
return pwsh.Invoke<T>();
}
finally
{
pwsh.Commands.Clear();
}
}
}
}
7 changes: 4 additions & 3 deletions PSCompatibilityAnalyzer/PSCompatibilityAnalyzer.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ PowerShellVersion = '3.0'

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(
'New-PowerShellCompatibilityProfile'
'Get-PlatformName'
'ConvertTo-CompatibilityJson'
'ConvertFrom-CompatibilityJson'
'Get-PowerShellCompatibilityProfileData'
Expand Down Expand Up @@ -74,7 +72,10 @@ FunctionsToExport = @(
)

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
CmdletsToExport = @(
'New-PSCompatibilityProfile'
'Get-PSCompatibilityPlatformName'
)

# Variables to export from this module
VariablesToExport = @()
Expand Down
Loading