Skip to content

Commit

Permalink
Fixes CLI module and assembly loading microsoft#1361 microsoft#1362 (m…
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Nov 27, 2022
1 parent 2ff19f5 commit 1f878e9
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 12 deletions.
8 changes: 8 additions & 0 deletions docs/CHANGELOG-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers

## Unreleased

What's changed since pre-release v2.7.0-B0016:

- Bug fixes:
- Fixes CLI failed to load required assemblies by @BernieWhite.
[#1361](https://github.com/microsoft/PSRule/issues/1361)
- Fixes CLI ignores modules specified in `Include.Modules` by @BernieWhite.
[#1362](https://github.com/microsoft/PSRule/issues/1362)

## v2.7.0-B0016 (pre-release)

What's changed since pre-release v2.7.0-B0006:
Expand Down
7 changes: 4 additions & 3 deletions src/PSRule.Tool/ClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ internal sealed class ClientHelper
{
public static void RunAnalyze(AnalyzerOptions operationOptions, ClientContext clientContext, InvocationContext invocation)
{
var option = GetOption();
var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug);
var option = GetOption(host);
var inputPath = operationOptions.InputPath == null || operationOptions.InputPath.Length == 0 ?
new string[] { PSRuleOption.GetWorkingPath() } : operationOptions.InputPath;

Expand All @@ -40,8 +40,8 @@ public static void RunAnalyze(AnalyzerOptions operationOptions, ClientContext cl

public static void RunRestore(RestoreOptions operationOptions, ClientContext clientContext, InvocationContext invocation)
{
var option = GetOption();
var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug);
var option = GetOption(host);
var requires = option.Requires.ToArray();

using var pwsh = PowerShell.Create();
Expand Down Expand Up @@ -97,8 +97,9 @@ private static void InstallVersion(PowerShell pwsh, string name, string version)
pwsh.Invoke();
}

private static PSRuleOption GetOption()
private static PSRuleOption GetOption(ClientHost host)
{
PSRuleOption.UseHostContext(host);
var option = PSRuleOption.FromFileOrEmpty();
option.Execution.InitialSessionState = Configuration.SessionState.Minimal;
option.Input.Format = InputFormat.File;
Expand Down
5 changes: 4 additions & 1 deletion src/PSRule.Tool/ClientHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.IO;
using System.Management.Automation;
using PSRule.Pipeline;

namespace PSRule.Tool
{
internal sealed class ClientHost : HostContext
{
private InvocationContext _Invocation;
private readonly InvocationContext _Invocation;
private readonly bool _Verbose;
private readonly bool _Debug;

Expand All @@ -20,6 +21,8 @@ public ClientHost(InvocationContext invocation, bool verbose, bool debug)
_Invocation = invocation;
_Verbose = verbose;
_Debug = debug;

Verbose($"Using working path: {Directory.GetCurrentDirectory()}");
}

public override ActionPreference GetPreferenceVariable(string variableName)
Expand Down
14 changes: 14 additions & 0 deletions src/PSRule/Configuration/PSRuleOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Threading;
using Newtonsoft.Json;
using PSRule.Definitions.Baselines;
using PSRule.Pipeline;
using PSRule.Resources;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
Expand Down Expand Up @@ -395,6 +396,19 @@ public static void UseExecutionContext(EngineIntrinsics executionContext)
_GetWorkingPath = () => executionContext.SessionState.Path.CurrentFileSystemLocation.Path;
}

/// <summary>
/// Set working path from a command-line host environment.
/// </summary>
public static void UseHostContext(IHostContext hostContext)
{
if (hostContext == null)
{
_GetWorkingPath = () => Directory.GetCurrentDirectory();
return;
}
_GetWorkingPath = () => hostContext.GetWorkingPath();
}

/// <summary>
/// Configures PSRule to use the culture of the current thread at runtime.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/PSRule/Definitions/IResultRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

namespace PSRule.Definitions
{
/// <summary>
/// A base interface for a PSRule result record.
/// </summary>
public interface IResultRecord
{
}
Expand Down
15 changes: 15 additions & 0 deletions src/PSRule/Definitions/IRuleResultV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public interface IRuleResultV2 : IResultRecord
long Time { get; }
}

/// <summary>
/// Detailed rule records for PSRule v2.
/// </summary>
public interface IDetailedRuleResultV2 : IRuleResultV2
{
/// <summary>
Expand All @@ -61,8 +64,14 @@ public interface IDetailedRuleResultV2 : IRuleResultV2
/// </summary>
Hashtable Field { get; }

/// <summary>
/// The bound name of the target.
/// </summary>
string TargetName { get; }

/// <summary>
/// The bound type of the target.
/// </summary>
string TargetType { get; }
}

Expand Down Expand Up @@ -92,8 +101,14 @@ public interface IResultReasonV2
/// </summary>
string FullPath { get; }

/// <summary>
/// The reason message.
/// </summary>
string Message { get; }

/// <summary>
/// Return a formatted reason string.
/// </summary>
string Format();
}
}
6 changes: 6 additions & 0 deletions src/PSRule/Pipeline/CommandLineBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public static IInvokePipelineBuilder Invoke(string[] module, PSRuleOption option
for (var i = 0; i < module.Length; i++)
sourcePipeline.ModuleByName(module[i]);

for (var i = 0; option.Include.Module != null && i < option.Include.Module.Length; i++)
sourcePipeline.ModuleByName(option.Include.Module[i]);

var source = sourcePipeline.Build();
var pipeline = new InvokeRulePipelineBuilder(source, hostContext);
pipeline.Configure(option);
Expand All @@ -49,6 +52,9 @@ public static IInvokePipelineBuilder Assert(string[] module, PSRuleOption option
for (var i = 0; module != null && i < module.Length; i++)
sourcePipeline.ModuleByName(module[i]);

for (var i = 0; option.Include.Module != null && i < option.Include.Module.Length; i++)
sourcePipeline.ModuleByName(option.Include.Module[i]);

var source = sourcePipeline.Build();
var pipeline = new AssertPipelineBuilder(source, hostContext);
pipeline.Configure(option);
Expand Down
10 changes: 10 additions & 0 deletions src/PSRule/Pipeline/GetRulePipelineBuiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@

namespace PSRule.Pipeline
{
/// <summary>
/// A helper to build a get pipeline.
/// </summary>
public interface IGetPipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Determines if the returned rules also include rule dependencies.
/// </summary>
void IncludeDependencies();
}

Expand All @@ -20,6 +26,7 @@ internal sealed class GetRulePipelineBuilder : PipelineBuilderBase, IGetPipeline
internal GetRulePipelineBuilder(Source[] source, IHostContext hostContext)
: base(source, hostContext) { }

/// <inheritdoc/>
public override IPipelineBuilder Configure(PSRuleOption option)
{
if (option == null)
Expand All @@ -36,11 +43,14 @@ public override IPipelineBuilder Configure(PSRuleOption option)

return this;
}

/// <inheritdoc/>
public void IncludeDependencies()
{
_IncludeDependencies = true;
}

/// <inheritdoc/>
public override IPipeline Build(IPipelineWriter writer = null)
{
return !RequireModules() || !RequireSources()
Expand Down
10 changes: 10 additions & 0 deletions src/PSRule/Pipeline/GetTargetPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@

namespace PSRule.Pipeline
{
/// <summary>
/// A helper to build a pipeline to return target objects.
/// </summary>
public interface IGetTargetPipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Specifies a path for reading input objects from disk.
/// </summary>
void InputPath(string[] path);
}

Expand All @@ -25,6 +31,7 @@ internal GetTargetPipelineBuilder(Source[] source, IHostContext hostContext)
_InputPath = null;
}

/// <inheritdoc/>
public override IPipelineBuilder Configure(PSRuleOption option)
{
if (option == null)
Expand All @@ -41,6 +48,7 @@ public override IPipelineBuilder Configure(PSRuleOption option)
return this;
}

/// <inheritdoc/>
public void InputPath(string[] path)
{
if (path == null || path.Length == 0)
Expand All @@ -58,11 +66,13 @@ public void InputPath(string[] path)
_InputPath = builder;
}

/// <inheritdoc/>
public override IPipeline Build(IPipelineWriter writer = null)
{
return new GetTargetPipeline(PrepareContext(null, null, null), PrepareReader(), writer ?? PrepareWriter());
}

/// <inheritdoc/>
protected override PipelineReader PrepareReader()
{
if (!string.IsNullOrEmpty(Option.Input.ObjectPath))
Expand Down
18 changes: 18 additions & 0 deletions src/PSRule/Pipeline/HostContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.IO;
using System.Management.Automation;
using PSRule.Definitions;

Expand Down Expand Up @@ -65,6 +66,11 @@ public interface IHostContext
/// Determines if a destructive action such as overwriting a file should be processed.
/// </summary>
bool ShouldProcess(string target, string action);

/// <summary>
/// Get the current working path.
/// </summary>
string GetWorkingPath();
}

internal static class HostContextExtensions
Expand Down Expand Up @@ -186,6 +192,12 @@ public virtual void Record(IResultRecord record)
{

}

/// <inheritdoc/>
public virtual string GetWorkingPath()
{
return Directory.GetCurrentDirectory();
}
}

/// <summary>
Expand Down Expand Up @@ -273,5 +285,11 @@ public void Object(object sendToPipeline, bool enumerateCollection)
{
CmdletContext.WriteObject(sendToPipeline, enumerateCollection);
}

/// <inheritdoc/>
public string GetWorkingPath()
{
return ExecutionContext.SessionState.Path.CurrentFileSystemLocation.Path;
}
}
}
27 changes: 23 additions & 4 deletions src/PSRule/Pipeline/SourcePipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,13 +294,14 @@ private static string[] SortModulePath(IEnumerable<string> values)
return results;
}

private static Source.ModuleInfo LoadManifest(string basePath)
private Source.ModuleInfo LoadManifest(string basePath)
{
var name = Path.GetFileName(Path.GetDirectoryName(basePath));
var path = Path.Combine(basePath, GetManifestName(name));
if (!File.Exists(path))
return null;

Log("Loading manifest for: {0}", basePath);
using var reader = new StreamReader(path);
var data = reader.ReadToEnd();
var ast = System.Management.Automation.Language.Parser.ParseInput(data, out _, out _);
Expand All @@ -316,14 +317,32 @@ private static Source.ModuleInfo LoadManifest(string basePath)
var projectUri = psData["ProjectUri"] as string;
var prerelease = psData["Prerelease"] as string;

if (manifest["RequiredAssemblies"] is Array requiredAssemblies)
if (TryRequiredAssemblies(manifest["RequiredAssemblies"], out var requiredAssemblies))
{
foreach (var a in requiredAssemblies.OfType<string>())
Assembly.LoadFile(Path.Combine(basePath, a));
foreach (var a in requiredAssemblies)
{
var assemblyPath = Path.Combine(basePath, a);
Log("Loading assembly: {0}", assemblyPath);
Assembly.LoadFile(assemblyPath);
}
}
return new Source.ModuleInfo(basePath, name, version, projectUri, guid, companyName, prerelease);
}

private static bool TryRequiredAssemblies(object value, out IEnumerable<string> requiredAssemblies)
{
requiredAssemblies = null;
if (value == null) return false;

if (value is string s)
requiredAssemblies = new string[] { s };

if (value is Array array)
requiredAssemblies = array.OfType<string>().ToArray();

return requiredAssemblies != null;
}

private static string GetManifestName(string name)
{
return string.Concat(name, ".psd1");
Expand Down
2 changes: 1 addition & 1 deletion src/PSRule/Pipeline/TargetObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal TargetObject(PSObject o, TargetSourceCollection source)
}

internal TargetObject(PSObject o, string targetName = null, string targetType = null, string scope = null)
: this (o, null)
: this(o, null)
{
if (targetName != null)
TargetName = targetName;
Expand Down
3 changes: 0 additions & 3 deletions tests/PSRule.Tests/SuppressionGroupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@
using System.Linq;
using System.Management.Automation;
using PSRule.Configuration;
using PSRule.Definitions;
using PSRule.Definitions.SuppressionGroups;
using PSRule.Host;
using PSRule.Pipeline;
using PSRule.Rules;
using PSRule.Runtime;
using Xunit;
using Assert = Xunit.Assert;
Expand Down

0 comments on commit 1f878e9

Please sign in to comment.