diff --git a/.vscode/launch.json b/.vscode/launch.json
index 251413c5d1..79c6a21b3c 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -74,6 +74,18 @@
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "integratedTerminal"
+ },
+ {
+ "name": "Run Extension",
+ "type": "extensionHost",
+ "request": "launch",
+ "args": [
+ "--extensionDevelopmentPath=${workspaceFolder}"
+ ],
+ "outFiles": [
+ "${workspaceFolder}/out/dist/**/*.js"
+ ],
+ "preLaunchTask": "Build vscode"
}
]
}
diff --git a/PSRule.sln b/PSRule.sln
index 9374f455dd..61f1f9d895 100644
--- a/PSRule.sln
+++ b/PSRule.sln
@@ -36,8 +36,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSRule.CommandLine.Tests",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSRule.Types.Tests", "tests\PSRule.Types.Tests\PSRule.Types.Tests.csproj", "{34095F78-CDA3-4E72-B64C-6366EA4B3EAF}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2E2A23CD-BD35-45DE-B2BD-20C8E45BBA41}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSRule.EditorServices", "src\PSRule.EditorServices\PSRule.EditorServices.csproj", "{061DD38A-B9E9-4EF1-B5B7-D0A484DB74D1}"
EndProject
Global
@@ -46,6 +44,14 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5FE4DB0B-63D1-4DDB-9762-9C0D29168BC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5FE4DB0B-63D1-4DDB-9762-9C0D29168BC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5FE4DB0B-63D1-4DDB-9762-9C0D29168BC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5FE4DB0B-63D1-4DDB-9762-9C0D29168BC9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {309BED8B-4E60-4C42-A2B4-37A2E7EBEF3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {309BED8B-4E60-4C42-A2B4-37A2E7EBEF3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {309BED8B-4E60-4C42-A2B4-37A2E7EBEF3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {309BED8B-4E60-4C42-A2B4-37A2E7EBEF3F}.Release|Any CPU.Build.0 = Release|Any CPU
{0130215D-58EB-4887-B6FA-31ED02500569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0130215D-58EB-4887-B6FA-31ED02500569}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0130215D-58EB-4887-B6FA-31ED02500569}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -58,10 +64,6 @@ Global
{D3488CE2-779F-4474-B38A-F894A4B689F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D3488CE2-779F-4474-B38A-F894A4B689F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3488CE2-779F-4474-B38A-F894A4B689F7}.Release|Any CPU.Build.0 = Release|Any CPU
- {309BED8B-4E60-4C42-A2B4-37A2E7EBEF3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {309BED8B-4E60-4C42-A2B4-37A2E7EBEF3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {309BED8B-4E60-4C42-A2B4-37A2E7EBEF3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {309BED8B-4E60-4C42-A2B4-37A2E7EBEF3F}.Release|Any CPU.Build.0 = Release|Any CPU
{20DDCC65-8A9A-4BDC-91EC-C3BE6F32E52E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20DDCC65-8A9A-4BDC-91EC-C3BE6F32E52E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20DDCC65-8A9A-4BDC-91EC-C3BE6F32E52E}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -74,10 +76,6 @@ Global
{BDDBFDB8-614F-4B8A-930C-DCB60144598C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDDBFDB8-614F-4B8A-930C-DCB60144598C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDDBFDB8-614F-4B8A-930C-DCB60144598C}.Release|Any CPU.Build.0 = Release|Any CPU
- {5FE4DB0B-63D1-4DDB-9762-9C0D29168BC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5FE4DB0B-63D1-4DDB-9762-9C0D29168BC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5FE4DB0B-63D1-4DDB-9762-9C0D29168BC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5FE4DB0B-63D1-4DDB-9762-9C0D29168BC9}.Release|Any CPU.Build.0 = Release|Any CPU
{872D2648-2F00-475E-84B5-F08BE07385B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{872D2648-2F00-475E-84B5-F08BE07385B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{872D2648-2F00-475E-84B5-F08BE07385B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -111,7 +109,6 @@ Global
{DA46C891-08F1-4D01-9F98-1F8BB10CAFEC} = {E0EA0CBA-96C5-4447-8B69-BC13EF0D7A4A}
{C25E2FC1-E306-4D99-925C-15E5DD51F6A2} = {E0EA0CBA-96C5-4447-8B69-BC13EF0D7A4A}
{34095F78-CDA3-4E72-B64C-6366EA4B3EAF} = {E0EA0CBA-96C5-4447-8B69-BC13EF0D7A4A}
- {061DD38A-B9E9-4EF1-B5B7-D0A484DB74D1} = {2E2A23CD-BD35-45DE-B2BD-20C8E45BBA41}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {533491EB-BAE9-472E-B57F-A675ECD335B5}
diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md
index e292b7c79f..1723898208 100644
--- a/docs/CHANGELOG-v3.md
+++ b/docs/CHANGELOG-v3.md
@@ -27,6 +27,18 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
## Unreleased
+What's changed since pre-release v3.0.0-B0340:
+
+- New features:
+ - VSCode extension includes PSRule runtime by @BernieWhite.
+ [#1755](https://github.com/microsoft/PSRule/issues/1755)
+ - The PSRule runtime is bundled with the VSCode extension.
+ - Separate installation of the PSRule PowerShell module is no longer required.
+ - VSCode extension asks to automatically restore modules by @BernieWhite.
+ [#2642](https://github.com/microsoft/PSRule/issues/2642)
+ - When opening a workspace, the extension will ask to restore any modules from the lock file.
+ - Alternatively, running the `PSRule: Restore modules` command manually will restore modules.
+
## v3.0.0-B0340 (pre-release)
What's changed since pre-release v3.0.0-B0315:
diff --git a/package.json b/package.json
index 8032e2c934..87d93f565c 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"onLanguage:powershell",
"onLanguage:yaml",
"workspaceContains:/ps-rule.yaml",
+ "workspaceContains:/ps-rule.lock.json",
"workspaceContains:**/ps-rule.yaml",
"workspaceContains:**/*.Rule.yaml",
"workspaceContains:**/*.Rule.yml",
@@ -196,16 +197,29 @@
"description": "Enables experimental features in the PSRule extension.",
"scope": "application"
},
+ "PSRule.lock.restore": {
+ "type": "boolean",
+ "default": false,
+ "description": "Determines if workspace modules will automatically be restored during activation. Modules can be restored manually using the PSRule: Restore modules command.",
+ "markdownDescription": "Determines if workspace modules will automatically be restored during activation. Modules can be restored manually using the `PSRule: Restore modules` command.",
+ "scope": "window"
+ },
"PSRule.notifications.showChannelUpgrade": {
"type": "boolean",
"default": true,
- "description": "Determines if a notification to switch to the stable channel is shown on start up.",
+ "description": "Determines if a notification to switch to the stable channel is shown on activation.",
+ "scope": "application"
+ },
+ "PSRule.notifications.showModuleRestore": {
+ "type": "boolean",
+ "default": true,
+ "description": "Determines if a notification to restore modules is shown on activation.",
"scope": "application"
},
"PSRule.notifications.showPowerShellExtension": {
"type": "boolean",
"default": true,
- "description": "Determines if a notification to install the PowerShell extension is shown on start up.",
+ "description": "Determines if a notification to install the PowerShell extension is shown on activation.",
"scope": "application"
},
"PSRule.options.path": {
diff --git a/src/PSRule.CommandLine/ClientContext.cs b/src/PSRule.CommandLine/ClientContext.cs
index 544c127ae8..0d68525a0d 100644
--- a/src/PSRule.CommandLine/ClientContext.cs
+++ b/src/PSRule.CommandLine/ClientContext.cs
@@ -61,7 +61,8 @@ public ClientContext(InvocationContext invocation, string? option, bool verbose,
public bool Debug { get; }
///
- /// Configures the path to use for caching rules modules.
+ /// Configures the root path to use for caching artifacts including modules.
+ /// Each artifact is in a subdirectory of the root path.
///
public string CachePath { get; }
diff --git a/src/PSRule.CommandLine/ClientHost.cs b/src/PSRule.CommandLine/ClientHost.cs
index 056ff31e10..ca500d1c96 100644
--- a/src/PSRule.CommandLine/ClientHost.cs
+++ b/src/PSRule.CommandLine/ClientHost.cs
@@ -135,4 +135,7 @@ public override void Debug(string text)
_Context.Invocation.Console.WriteLine(text);
}
+
+ ///
+ public override string? CachePath => _Context.CachePath;
}
diff --git a/src/PSRule.EditorServices/ClientBuilder.cs b/src/PSRule.EditorServices/ClientBuilder.cs
index 96bc77d4ff..3fe5549444 100644
--- a/src/PSRule.EditorServices/ClientBuilder.cs
+++ b/src/PSRule.EditorServices/ClientBuilder.cs
@@ -37,6 +37,7 @@ internal sealed class ClientBuilder
private readonly Option _Run_Module;
private readonly Option _Run_Baseline;
private readonly Option _Run_Outcome;
+ private readonly Option _Run_NoRestore;
private ClientBuilder(RootCommand cmd)
{
@@ -87,6 +88,10 @@ private ClientBuilder(RootCommand cmd)
description: CmdStrings.Run_Outcome_Description
).FromAmong("Pass", "Fail", "Error", "Processed", "Problem");
_Run_Outcome.Arity = ArgumentArity.ZeroOrMore;
+ _Run_NoRestore = new Option(
+ "--no-restore",
+ description: CmdStrings.Run_NoRestore_Description
+ );
// Options for the module command.
_Module_Init_Force = new Option(
@@ -144,6 +149,7 @@ private void AddRun()
cmd.AddOption(_Run_Module);
cmd.AddOption(_Run_Baseline);
cmd.AddOption(_Run_Outcome);
+ cmd.AddOption(_Run_NoRestore);
cmd.SetHandler(async (invocation) =>
{
var option = new RunOptions
@@ -153,6 +159,7 @@ private void AddRun()
Module = invocation.ParseResult.GetValueForOption(_Run_Module),
Baseline = invocation.ParseResult.GetValueForOption(_Run_Baseline),
Outcome = ParseOutcome(invocation.ParseResult.GetValueForOption(_Run_Outcome)),
+ NoRestore = invocation.ParseResult.GetValueForOption(_Run_NoRestore),
};
var client = GetClientContext(invocation);
@@ -164,6 +171,7 @@ private void AddRun()
invocation.Console.WriteLine($"VERBOSE: Using workspace: {Environment.GetWorkingPath()}");
invocation.Console.WriteLine($"VERBOSE: Using module search path: {sp}");
invocation.Console.WriteLine($"VERBOSE: Using language server: {client.Path}");
+ invocation.Console.WriteLine($"VERBOSE: Using cache path: {client.CachePath}");
}
invocation.ExitCode = await RunCommand.RunAsync(option, client);
diff --git a/src/PSRule.EditorServices/Resources/CmdStrings.Designer.cs b/src/PSRule.EditorServices/Resources/CmdStrings.Designer.cs
index ef26633884..462264a5ab 100644
--- a/src/PSRule.EditorServices/Resources/CmdStrings.Designer.cs
+++ b/src/PSRule.EditorServices/Resources/CmdStrings.Designer.cs
@@ -258,6 +258,15 @@ internal static string Run_Module_Description {
}
}
+ ///
+ /// Looks up a localized string similar to Do not restore modules before running rules..
+ ///
+ internal static string Run_NoRestore_Description {
+ get {
+ return ResourceManager.GetString("Run_NoRestore_Description", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Specifies the rule results to show in output. By default, Pass/ Fail/ Error results are shown..
///
diff --git a/src/PSRule.EditorServices/Resources/CmdStrings.resx b/src/PSRule.EditorServices/Resources/CmdStrings.resx
index 4df7b89b9d..8964becd37 100644
--- a/src/PSRule.EditorServices/Resources/CmdStrings.resx
+++ b/src/PSRule.EditorServices/Resources/CmdStrings.resx
@@ -192,4 +192,7 @@
Specifies a path to write results to.
+
+ Do not restore modules before running rules.
+
\ No newline at end of file
diff --git a/src/PSRule/Pipeline/CommandLineBuilder.cs b/src/PSRule/Pipeline/CommandLineBuilder.cs
index c49b2c51f3..cf2d1fd975 100644
--- a/src/PSRule/Pipeline/CommandLineBuilder.cs
+++ b/src/PSRule/Pipeline/CommandLineBuilder.cs
@@ -26,7 +26,7 @@ public static class CommandLineBuilder
/// A builder object to configure the pipeline.
public static IInvokePipelineBuilder Invoke(string[] module, PSRuleOption option, IHostContext hostContext, LockFile? file = null)
{
- var sourcePipeline = new SourcePipelineBuilder(hostContext, option, GetLocalPath());
+ var sourcePipeline = new SourcePipelineBuilder(hostContext, option, hostContext.CachePath ?? GetLocalPath());
LoadModules(sourcePipeline, module, option, file);
var source = sourcePipeline.Build();
diff --git a/src/PSRule/Pipeline/HostContext.cs b/src/PSRule/Pipeline/HostContext.cs
index d6b7dc7a2f..0e92131ad9 100644
--- a/src/PSRule/Pipeline/HostContext.cs
+++ b/src/PSRule/Pipeline/HostContext.cs
@@ -6,6 +6,8 @@
namespace PSRule.Pipeline;
+#nullable enable
+
///
/// A base class for custom host context instances.
///
@@ -94,4 +96,9 @@ public virtual string GetWorkingPath()
{
return Directory.GetCurrentDirectory();
}
+
+ ///
+ public virtual string? CachePath => null;
}
+
+#nullable restore
diff --git a/src/PSRule/Pipeline/IHostContext.cs b/src/PSRule/Pipeline/IHostContext.cs
index 44997fc0e5..761a0b3407 100644
--- a/src/PSRule/Pipeline/IHostContext.cs
+++ b/src/PSRule/Pipeline/IHostContext.cs
@@ -5,6 +5,8 @@
namespace PSRule.Pipeline;
+#nullable enable
+
///
/// A host context for handling input and output emitted from the pipeline.
///
@@ -76,4 +78,12 @@ public interface IHostContext
/// Get the current working path.
///
string GetWorkingPath();
+
+ ///
+ /// Configures the root path to use for caching artifacts including modules.
+ /// Each artifact is in a subdirectory of the root path.
+ ///
+ string? CachePath { get; }
}
+
+#nullable restore
diff --git a/src/PSRule/Pipeline/PSHostContext.cs b/src/PSRule/Pipeline/PSHostContext.cs
index d38859c328..122b99aa23 100644
--- a/src/PSRule/Pipeline/PSHostContext.cs
+++ b/src/PSRule/Pipeline/PSHostContext.cs
@@ -5,6 +5,8 @@
namespace PSRule.Pipeline;
+#nullable enable
+
///
/// The host context used for PowerShell-based pipelines.
///
@@ -98,4 +100,9 @@ public string GetWorkingPath()
{
return ExecutionContext.SessionState.Path.CurrentFileSystemLocation.Path;
}
+
+ ///
+ public string? CachePath { get; }
}
+
+#nullable restore
diff --git a/src/PSRule/Pipeline/SourcePipelineBuilder.cs b/src/PSRule/Pipeline/SourcePipelineBuilder.cs
index 04d7aee703..a3a9843985 100644
--- a/src/PSRule/Pipeline/SourcePipelineBuilder.cs
+++ b/src/PSRule/Pipeline/SourcePipelineBuilder.cs
@@ -30,18 +30,18 @@ public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceComma
private readonly IHostContext _HostContext;
private readonly HostPipelineWriter _Writer;
private readonly bool _UseDefaultPath;
- private readonly string _LocalPath;
+ private readonly string _CachePath;
private readonly RestrictScriptSource _RestrictScriptSource;
private readonly string _WorkspacePath;
- internal SourcePipelineBuilder(IHostContext hostContext, PSRuleOption option, string localPath = null)
+ internal SourcePipelineBuilder(IHostContext hostContext, PSRuleOption option, string cachePath = null)
{
_Source = new Dictionary(StringComparer.OrdinalIgnoreCase);
_HostContext = hostContext;
_Writer = new HostPipelineWriter(hostContext, option, ShouldProcess);
_Writer.EnterScope("[Discovery.Source]");
_UseDefaultPath = option == null || option.Include == null || option.Include.Path == null;
- _LocalPath = localPath;
+ _CachePath = cachePath;
_RestrictScriptSource = option?.Execution?.RestrictScriptSource ?? ExecutionOption.Default.RestrictScriptSource.Value;
_WorkspacePath = Environment.GetRootedBasePath(null);
@@ -162,32 +162,29 @@ public void ModuleByName(string name, string version = null)
private string FindModule(string name, string version)
{
- return TryPackagedModule(name, version, out var path) ||
+ return TryPackagedModuleFromCache(name, version, out var path) ||
TryInstalledModule(name, version, out path) ? path : null;
}
///
/// Try to find a packaged module found relative to the tool.
///
- private bool TryPackagedModule(string name, string version, out string path)
+ private bool TryPackagedModuleFromCache(string name, string version, out string path)
{
path = null;
- if (_LocalPath == null)
+ if (_CachePath == null)
return false;
- Log($"[PSRule][S] -- Searching for module in: {_LocalPath}");
+ Log($"[PSRule][S] -- Searching for module in: {_CachePath}");
if (!string.IsNullOrEmpty(version))
{
- path = Environment.GetRootedBasePath(Path.Combine(_LocalPath, "Modules", name, version));
- if (System.IO.Directory.Exists(path))
+ path = Environment.GetRootedBasePath(Path.Combine(_CachePath, "Modules", name, version));
+ if (File.Exists(Path.Combine(path, GetManifestName(name))))
return true;
}
- path = Environment.GetRootedBasePath(Path.Combine(_LocalPath, "Modules", name));
- if (System.IO.Directory.Exists(path))
- return true;
-
- return System.IO.Directory.Exists(path);
+ path = Environment.GetRootedBasePath(Path.Combine(_CachePath, "Modules", name));
+ return File.Exists(Path.Combine(path, GetManifestName(name)));
}
///
@@ -253,10 +250,10 @@ private static string[] SortModulePath(IEnumerable values)
private Source.ModuleInfo LoadManifest(string basePath, string name)
{
var path = Path.Combine(basePath, GetManifestName(name));
+ Log("[PSRule][S] -- Loading manifest from: {0}", path);
if (!File.Exists(path))
return null;
- Log("[PSRule][S] -- 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 _);
diff --git a/src/vscode-ps-rule/README.md b/src/vscode-ps-rule/README.md
index 5832288a0a..c83071157c 100644
--- a/src/vscode-ps-rule/README.md
+++ b/src/vscode-ps-rule/README.md
@@ -77,8 +77,10 @@ Name | Description
`PSRule.execution.ruleSuppressed` | Determines how to handle suppressed rules. When set to `None`, PSRule will use the default (`Warn`), unless set by PSRule options.
`PSRule.execution.unprocessedObject` | Determines how to report objects that are not processed by any rule. When set to `None`, PSRule will use the default (`Warn`), unless set by PSRule options.
`PSRule.experimental.enabled` | Enables experimental features in the PSRule extension.
-`PSRule.notifications.showChannelUpgrade` | Determines if a notification to switch to the stable channel is shown on start up.
-`PSRule.notifications.showPowerShellExtension` | Determines if a notification to install the PowerShell extension is shown on start up.
+`PSRule.lock.restore` | Determines if workspace modules will automatically be restored during activation. Modules can be restored manually using the `PSRule: Restore modules` command.
+`PSRule.notifications.showChannelUpgrade` | Determines if a notification to switch to the stable channel is shown on activation.
+`PSRule.notifications.showModuleRestore` | Determines if a notification to restore modules is shown on activation.
+`PSRule.notifications.showPowerShellExtension` | Determines if a notification to install the PowerShell extension is shown on activation.
`PSRule.options.path` | The path specifying a PSRule option file. When not set, the default `ps-rule.yaml` will be used from the current workspace.
`PSRule.output.as` | Configures the output of analysis tasks, either summary or detailed.
`PSRule.rule.baseline` | The name of the default baseline to use for executing rules. This setting can be overridden on individual PSRule tasks.
diff --git a/src/vscode-ps-rule/commands/restore.ts b/src/vscode-ps-rule/commands/restore.ts
index fa96704073..f610612f0d 100644
--- a/src/vscode-ps-rule/commands/restore.ts
+++ b/src/vscode-ps-rule/commands/restore.ts
@@ -2,7 +2,7 @@
// Licensed under the MIT License.
import * as cp from 'child_process';
-import { workspace } from 'vscode';
+import { workspace, window, ProgressLocation } from 'vscode';
import { getActiveOrFirstWorkspace } from '../utils';
import { logger } from '../logger';
import { ext } from '../extension';
@@ -18,30 +18,43 @@ export async function restore(): Promise {
logger.log('Restoring modules.');
- const tool = cp.spawnSync(server.binPath, [server.languageServerPath, 'module', 'restore', '--verbose'], {
- cwd: workspace.uri.fsPath,
- });
+ await window.withProgress({
+ location: ProgressLocation.Window,
+ title: 'PSRule',
+ cancellable: false,
+ }, async (progress) => {
+
+ progress.report({ message: 'Restoring modules' });
- tool.output?.forEach(o => {
-
- o?.toString().split('\n').forEach(line => {
- if (line.startsWith('VERBOSE:')) {
- logger.verbose(line);
- }
- else if (line.startsWith('ERROR:')) {
- logger.error(line);
- }
- else {
- logger.log(line);
- }
+ const tool = cp.spawnSync(server.binPath, [server.languageServerPath, 'module', 'restore', '--verbose'], {
+ cwd: workspace.uri.fsPath,
});
- });
- if (tool.status !== 0) {
- logger.log(`Failed to restore modules. Exit code: ${tool.status}`);
- return;
- }
- else {
- logger.log('Modules restored.');
- }
+ progress.report({ message: 'Restoring modules' });
+
+ tool.output?.forEach(o => {
+
+ o?.toString().split('\n').forEach(line => {
+ if (line.startsWith('VERBOSE:')) {
+ logger.verbose(line);
+ }
+ else if (line.startsWith('ERROR:')) {
+ logger.error(line);
+ }
+ else {
+ logger.log(line);
+ }
+ });
+ });
+
+ if (tool.status !== 0) {
+ logger.log(`Failed to restore modules. Exit code: ${tool.status}`);
+ return;
+ }
+ else {
+ logger.log('Modules restored.');
+ }
+
+ progress.report({ message: 'Completed restore', increment: 100 });
+ });
}
diff --git a/src/vscode-ps-rule/configuration.ts b/src/vscode-ps-rule/configuration.ts
index ea4c153886..b8f77cbc63 100644
--- a/src/vscode-ps-rule/configuration.ts
+++ b/src/vscode-ps-rule/configuration.ts
@@ -81,6 +81,11 @@ export interface ISetting {
*/
experimentalEnabled: boolean;
+ /**
+ * Determines if PSRule will automatically restore modules.
+ */
+ lockRestore: boolean;
+
/**
* The path specifying a PSRule option file.
* When not set, the default ps-rule.yaml will be used from the current workspace.
@@ -88,7 +93,20 @@ export interface ISetting {
optionsPath: string | undefined;
outputAs: OutputAs;
+
+ /**
+ * Determines if a notification to switch to the stable channel is shown on activation.
+ */
notificationsShowChannelUpgrade: boolean;
+
+ /**
+ * Determines if a notification to restore modules is shown on activation.
+ */
+ notificationsShowModuleRestore: boolean;
+
+ /**
+ * Determines if a notification to install the PowerShell extension is shown on activation.
+ */
notificationsShowPowerShellExtension: boolean;
/**
@@ -120,9 +138,11 @@ const globalDefaults: ISetting = {
executionRuleSuppressed: ExecutionActionPreference.None,
executionUnprocessedObject: ExecutionActionPreference.None,
experimentalEnabled: false,
+ lockRestore: false,
optionsPath: undefined,
outputAs: OutputAs.Summary,
notificationsShowChannelUpgrade: true,
+ notificationsShowModuleRestore: true,
notificationsShowPowerShellExtension: true,
ruleBaseline: undefined,
// languageServerPath: undefined,
@@ -206,6 +226,8 @@ export class ConfigurationManager {
this.current.executionRuleSuppressed = config.get('execution.ruleSuppressed', this.default.executionRuleSuppressed);
this.current.executionUnprocessedObject = config.get('execution.unprocessedObject', this.default.executionUnprocessedObject);
+ this.current.lockRestore = config.get('lock.restore') ?? this.default.lockRestore;
+
this.current.optionsPath = config.get('options.path') ?? this.default.optionsPath;
this.current.outputAs = config.get('output.as', this.default.outputAs);
@@ -215,6 +237,11 @@ export class ConfigurationManager {
this.default.notificationsShowChannelUpgrade
);
+ this.current.notificationsShowModuleRestore = config.get(
+ 'notifications.showModuleRestore',
+ this.default.notificationsShowModuleRestore
+ );
+
this.current.notificationsShowPowerShellExtension = config.get(
'notifications.showPowerShellExtension',
this.default.notificationsShowPowerShellExtension
diff --git a/src/vscode-ps-rule/extension.ts b/src/vscode-ps-rule/extension.ts
index 545a595cc0..0e6b52ebff 100644
--- a/src/vscode-ps-rule/extension.ts
+++ b/src/vscode-ps-rule/extension.ts
@@ -4,10 +4,11 @@
'use strict';
import * as path from 'path';
+import * as fse from 'fs-extra';
import * as vscode from 'vscode';
import { logger } from './logger';
import { PSRuleTaskProvider } from './tasks';
-import { ConfigurationManager } from './configuration';
+import { configuration, ConfigurationManager } from './configuration';
import { pwsh } from './powershell';
import { DocumentationLensProvider } from './docLens';
import { createOptionsFile } from './commands/createOptionsFile';
@@ -16,7 +17,7 @@ import { walkthroughCopySnippet } from './commands/walkthroughCopySnippet';
import { configureSettings } from './commands/configureSettings';
import { runAnalysisTask } from './commands/runAnalysisTask';
import { showTasks } from './commands/showTasks';
-import { PSRuleLanguageServer, getLanguageServer } from './utils';
+import { PSRuleLanguageServer, getActiveOrFirstWorkspace, getLanguageServer } from './utils';
import { restore } from './commands/restore';
export let taskManager: PSRuleTaskProvider | undefined;
@@ -159,6 +160,8 @@ export class ExtensionManager implements vscode.Disposable {
if (this.isTrusted) {
this._server = await getLanguageServer(this._context);
+
+ await this.restoreOnActivation()
}
}
@@ -284,6 +287,62 @@ export class ExtensionManager implements vscode.Disposable {
};
return result;
}
+
+ private async restoreOnActivation(): Promise {
+ const workspace = getActiveOrFirstWorkspace();
+ if (!workspace) {
+ return;
+ }
+
+ // Check if a lockfile exists.
+ const lockExists = await fse.exists(path.join(workspace.uri.fsPath, 'ps-rule.lock.json'));
+ if (!lockExists) {
+ return;
+ }
+
+ // Check if the user has disabled the notification.
+ const shouldPrompt = configuration.get().notificationsShowModuleRestore
+
+ // Check if restoring module is enabled.
+ let lockRestore = configuration.get().lockRestore
+
+ // If restore is not enabled then prompt the user to enable it.
+ if (!lockRestore && shouldPrompt) {
+ const always = 'Always';
+ const oneTime = 'One Time';
+ const alwaysIgnore = 'Always Ignore';
+
+ await vscode.window
+ .showInformationMessage(
+ `Should PSRule automatically restore modules in your current workspace?`,
+ always,
+ oneTime,
+ alwaysIgnore
+ )
+ .then((choice) => {
+ if (choice === always) {
+ vscode.workspace
+ .getConfiguration('PSRule.lock')
+ .update('restore', true, vscode.ConfigurationTarget.Global);
+
+ lockRestore = true;
+ }
+ if (choice === oneTime) {
+ lockRestore = true;
+ }
+ if (choice === alwaysIgnore) {
+ vscode.workspace
+ .getConfiguration('PSRule.notifications')
+ .update('showModuleRestore', false, vscode.ConfigurationTarget.Global);
+ }
+ });
+ }
+
+ // Restore modules.
+ if (lockRestore) {
+ await restore();
+ }
+ }
}
export const ext: ExtensionManager = new ExtensionManager();
diff --git a/src/vscode-ps-rule/runner.ts b/src/vscode-ps-rule/runner.ts
new file mode 100644
index 0000000000..8a6ec5eeef
--- /dev/null
+++ b/src/vscode-ps-rule/runner.ts
@@ -0,0 +1,125 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+'use strict';
+
+import * as vscode from 'vscode';
+import { ISetting, ExecutionActionPreference, TraceLevelPreference } from './configuration';
+import { getActiveOrFirstWorkspace } from './utils';
+
+export function getAnalysisRunner(
+ folder: vscode.WorkspaceFolder | undefined,
+ configuration: ISetting,
+ binPath: string,
+ languageServerPath: string,
+ path?: string,
+ inputPath?: string,
+ baseline?: string,
+ modules?: string[],
+ outcome?: string[],
+): vscode.ProcessExecution {
+ const executionRuleExcluded = configuration.executionRuleExcluded;
+ const executionRuleSuppressed = configuration.executionRuleSuppressed;
+ const executionUnprocessedObject = configuration.executionUnprocessedObject;
+ const lockRestore = configuration.lockRestore;
+ const optionsPath = configuration.optionsPath;
+ const outputAs = configuration.outputAs;
+ const ruleBaseline = configuration.ruleBaseline;
+ const traceTask = configuration.traceTask;
+
+ function getCmdTooling(): string[] {
+ let params: string[] = [];
+
+ // Path
+ if (path !== undefined && path !== '') {
+ params.push('--path');
+ params.push(`'${path}'`);
+ }
+
+ // Options Path
+ if (optionsPath !== undefined && optionsPath !== '') {
+ params.push('--option');
+ params.push(`'${optionsPath}'`);
+ }
+
+ // Input Path
+ if (inputPath !== undefined && inputPath !== '') {
+ params.push('--input-path');
+ params.push(inputPath);
+ } else {
+ params.push('--input-path');
+ params.push('.');
+ }
+
+ // Baseline
+ if (baseline !== undefined && baseline !== '') {
+ params.push('--baseline');
+ params.push(`'${baseline}'`);
+ } else if (ruleBaseline !== undefined && ruleBaseline !== '') {
+ params.push('--baseline');
+ params.push(`'${ruleBaseline}'`);
+ }
+
+ // Modules
+ if (modules !== undefined && modules.length > 0) {
+ for (let i = 0; i < modules.length; i++) {
+ params.push('--module');
+ params.push(`'${modules[i]}'`);
+ }
+ }
+
+ // Outcome
+ if (outcome !== undefined && outcome.length > 0) {
+ for (let i = 0; i < outcome.length; i++) {
+ params.push('--outcome');
+ params.push(outcome[i]);
+ }
+ }
+ else {
+ params.push('--outcome');
+ params.push('Fail');
+ params.push('--outcome');
+ params.push('Error');
+ }
+
+ // Toggle module restore
+ if (!lockRestore) {
+ params.push('--no-restore');
+ }
+
+ return params;
+ }
+
+ // Set environment variables for the task.
+ let taskEnv: { [key: string]: string } = {
+ PSRULE_OUTPUT_STYLE: 'VisualStudioCode',
+ PSRULE_OUTPUT_AS: outputAs,
+ PSRULE_OUTPUT_CULTURE: vscode.env.language,
+ PSRULE_OUTPUT_BANNER: 'Minimal',
+ };
+
+ if (executionRuleExcluded !== undefined && executionRuleExcluded !== ExecutionActionPreference.None) {
+ taskEnv.PSRULE_EXECUTION_RULEEXCLUDED = executionRuleExcluded;
+ }
+
+ if (executionRuleSuppressed !== undefined && executionRuleSuppressed !== ExecutionActionPreference.None) {
+ taskEnv.PSRULE_EXECUTION_RULESUPPRESSED = executionRuleSuppressed;
+ }
+
+ if (executionUnprocessedObject !== undefined && executionUnprocessedObject !== ExecutionActionPreference.None) {
+ taskEnv.PSRULE_EXECUTION_UNPROCESSEDOBJECT = executionUnprocessedObject;
+ }
+
+ const cwd = folder?.uri.fsPath ?? getActiveOrFirstWorkspace()?.uri.fsPath;
+ const args = [languageServerPath, 'run'];
+ args.push(...getCmdTooling());
+ if (traceTask === TraceLevelPreference.Verbose) {
+ args.push('--verbose');
+ }
+
+ return new vscode.ProcessExecution(
+ binPath,
+ args,
+ { cwd: cwd, env: taskEnv },
+ )
+}
diff --git a/src/vscode-ps-rule/tasks.ts b/src/vscode-ps-rule/tasks.ts
index cc8015fdc1..f96eb8b6a3 100644
--- a/src/vscode-ps-rule/tasks.ts
+++ b/src/vscode-ps-rule/tasks.ts
@@ -8,9 +8,9 @@ import * as path from 'path';
import * as vscode from 'vscode';
import { defaultOptionsFile } from './consts';
import { ILogger, logger } from './logger';
-import { configuration, ExecutionActionPreference, TraceLevelPreference } from './configuration';
-import { getActiveOrFirstWorkspace } from './utils';
+import { configuration } from './configuration';
import { ext } from './extension';
+import { getAnalysisRunner } from './runner';
const emptyTasks: vscode.Task[] = [];
@@ -219,76 +219,10 @@ export class PSRuleTaskProvider implements vscode.TaskProvider {
};
}
- const executionRuleExcluded = configuration.get().executionRuleExcluded;
- const executionRuleSuppressed = configuration.get().executionRuleSuppressed;
- const executionUnprocessedObject = configuration.get().executionUnprocessedObject;
- const optionsPath = configuration.get().optionsPath;
- const outputAs = configuration.get().outputAs;
- const ruleBaseline = configuration.get().ruleBaseline;
- const traceTask = configuration.get().traceTask;
-
function getTaskName() {
return name;
}
- function getCmdTooling(): string[] {
- let params: string[] = [];
-
- // Path
- if (path !== undefined && path !== '') {
- params.push('--path');
- params.push(`'${path}'`);
- }
-
- // Options Path
- if (optionsPath !== undefined && optionsPath !== '') {
- params.push('--option');
- params.push(`'${optionsPath}'`);
- }
-
- // Input Path
- if (inputPath !== undefined && inputPath !== '') {
- params.push('--input-path');
- params.push(inputPath);
- } else {
- params.push('--input-path');
- params.push('.');
- }
-
- // Baseline
- if (baseline !== undefined && baseline !== '') {
- params.push('--baseline');
- params.push(`'${baseline}'`);
- } else if (ruleBaseline !== undefined && ruleBaseline !== '') {
- params.push('--baseline');
- params.push(`'${ruleBaseline}'`);
- }
-
- // Modules
- if (modules !== undefined && modules.length > 0) {
- for (let i = 0; i < modules.length; i++) {
- params.push('--module');
- params.push(`'${modules[i]}'`);
- }
- }
-
- // Outcome
- if (outcome !== undefined && outcome.length > 0) {
- for (let i = 0; i < outcome.length; i++) {
- params.push('--outcome');
- params.push(outcome[i]);
- }
- }
- else {
- params.push('--outcome');
- params.push('Fail');
- params.push('--outcome');
- params.push('Error');
- }
-
- return params;
- }
-
const taskName = getTaskName();
const binPath = ext.server?.binPath;
const languageServerPath = ext.server?.languageServerPath
@@ -307,32 +241,7 @@ export class PSRuleTaskProvider implements vscode.TaskProvider {
);
}
- // Set environment variables for the task.
- let taskEnv: { [key: string]: string } = {
- PSRULE_OUTPUT_STYLE: 'VisualStudioCode',
- PSRULE_OUTPUT_AS: outputAs,
- PSRULE_OUTPUT_CULTURE: vscode.env.language,
- PSRULE_OUTPUT_BANNER: 'Minimal',
- };
-
- if (executionRuleExcluded !== undefined && executionRuleExcluded !== ExecutionActionPreference.None) {
- taskEnv.PSRULE_EXECUTION_RULEEXCLUDED = executionRuleExcluded;
- }
-
- if (executionRuleSuppressed !== undefined && executionRuleSuppressed !== ExecutionActionPreference.None) {
- taskEnv.PSRULE_EXECUTION_RULESUPPRESSED = executionRuleSuppressed;
- }
-
- if (executionUnprocessedObject !== undefined && executionUnprocessedObject !== ExecutionActionPreference.None) {
- taskEnv.PSRULE_EXECUTION_UNPROCESSEDOBJECT = executionUnprocessedObject;
- }
-
- const cwd = folder?.uri.fsPath ?? getActiveOrFirstWorkspace()?.uri.fsPath;
- const args = [languageServerPath, 'run'];
- args.push(...getCmdTooling());
- if (traceTask === TraceLevelPreference.Verbose) {
- args.push('--verbose');
- }
+ const runner = getAnalysisRunner(folder, configuration.get(), binPath, languageServerPath, path, inputPath, baseline, modules, outcome);
// Return the task instance.
const t = new vscode.Task(
@@ -340,11 +249,7 @@ export class PSRuleTaskProvider implements vscode.TaskProvider {
folder ?? vscode.TaskScope.Workspace,
taskName,
PSRuleTaskProvider.taskType,
- new vscode.ProcessExecution(
- binPath,
- args,
- { cwd: cwd, env: taskEnv },
- ),
+ runner,
matcher,
);
t.detail = 'Run analysis for current workspace.';
@@ -352,7 +257,7 @@ export class PSRuleTaskProvider implements vscode.TaskProvider {
echo: false,
};
- const parameterArgs = args.slice(1);
+ const parameterArgs = runner.args.slice(1);
logger.verbose(`Preparing task '${taskName}' with arguments: ${parameterArgs.join(' ')}`);
return t;
}
diff --git a/src/vscode-ps-rule/test/suite/configuration.test.ts b/src/vscode-ps-rule/test/suite/configuration.test.ts
index 570259f879..a123127e05 100644
--- a/src/vscode-ps-rule/test/suite/configuration.test.ts
+++ b/src/vscode-ps-rule/test/suite/configuration.test.ts
@@ -17,8 +17,10 @@ suite('ConfigurationManager tests', () => {
assert.equal(config.get().executionRuleSuppressed, ExecutionActionPreference.None);
assert.equal(config.get().executionUnprocessedObject, ExecutionActionPreference.None);
assert.equal(config.get().experimentalEnabled, false);
+ assert.equal(config.get().lockRestore, false);
assert.equal(config.get().outputAs, OutputAs.Summary);
assert.equal(config.get().notificationsShowChannelUpgrade, true);
+ assert.equal(config.get().notificationsShowModuleRestore, true);
assert.equal(config.get().notificationsShowPowerShellExtension, true);
assert.equal(config.get().ruleBaseline, undefined);
//assert.equal(config.get().languageServerPath, false);