Skip to content

Commit 650b5c2

Browse files
author
Kapil Borle
authored
Merge pull request #614 from PowerShell/kapilmb/SettingsAutoDiscovery
Discover settings file from given path
2 parents dcb94b3 + 20ab622 commit 650b5c2

File tree

8 files changed

+134
-12
lines changed

8 files changed

+134
-12
lines changed

Engine/Commands/InvokeScriptAnalyzerCommand.cs

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands
3838
HelpUri = "http://go.microsoft.com/fwlink/?LinkId=525914")]
3939
public class InvokeScriptAnalyzerCommand : PSCmdlet, IOutputWriter
4040
{
41+
#region Private variables
42+
List<string> processedPaths;
43+
#endregion // Private variables
44+
4145
#region Parameters
4246
/// <summary>
4347
/// Path: The path to the file or folder to invoke PSScriptAnalyzer on.
@@ -218,9 +222,63 @@ protected override void BeginProcessing()
218222

219223
string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath,
220224
this.SessionState, recurseCustomRulePath);
225+
if (IsFileParameterSet())
226+
{
227+
ProcessPath();
228+
}
229+
230+
var settingFileHasErrors = false;
231+
if (settings == null
232+
&& processedPaths != null
233+
&& processedPaths.Count == 1)
234+
{
235+
// add a directory separator character because if there is no trailing separator character, it will return the parent
236+
var directory = processedPaths[0].TrimEnd(System.IO.Path.DirectorySeparatorChar);
237+
if (File.Exists(directory))
238+
{
239+
// if given path is a file, get its directory
240+
directory = System.IO.Path.GetDirectoryName(directory);
241+
}
242+
243+
this.WriteVerbose(
244+
String.Format(
245+
"Settings not provided. Will look for settings file in the given path {0}.",
246+
path));
247+
var settingsFileAutoDiscovered = false;
248+
if (Directory.Exists(directory))
249+
{
250+
// if settings are not provided explicitly, look for it in the given path
251+
// check if pssasettings.psd1 exists
252+
var settingsFilename = "PSScriptAnalyzerSettings.psd1";
253+
var settingsFilepath = System.IO.Path.Combine(directory, settingsFilename);
254+
if (File.Exists(settingsFilepath))
255+
{
256+
settingsFileAutoDiscovered = true;
257+
this.WriteVerbose(
258+
String.Format(
259+
"Found {0} in {1}. Will use it to provide settings for this invocation.",
260+
settingsFilename,
261+
directory));
262+
settingFileHasErrors = !ScriptAnalyzer.Instance.ParseProfile(settingsFilepath, this.SessionState.Path, this);
263+
}
264+
}
221265

222-
if (!ScriptAnalyzer.Instance.ParseProfile(this.settings, this.SessionState.Path, this))
266+
if (!settingsFileAutoDiscovered)
267+
{
268+
this.WriteVerbose(
269+
String.Format(
270+
"Cannot find a settings file in the given path {0}.",
271+
path));
272+
}
273+
}
274+
else
223275
{
276+
settingFileHasErrors = !ScriptAnalyzer.Instance.ParseProfile(this.settings, this.SessionState.Path, this);
277+
}
278+
279+
if (settingFileHasErrors)
280+
{
281+
this.WriteWarning("Cannot parse settings. Will abort the invocation.");
224282
stopProcessing = true;
225283
return;
226284
}
@@ -287,15 +345,11 @@ protected override void StopProcessing()
287345
private void ProcessInput()
288346
{
289347
IEnumerable<DiagnosticRecord> diagnosticsList = Enumerable.Empty<DiagnosticRecord>();
290-
if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase))
348+
if (IsFileParameterSet())
291349
{
292-
// throws Item Not Found Exception
293-
Collection<PathInfo> paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path);
294-
foreach (PathInfo p in paths)
350+
foreach (var p in processedPaths)
295351
{
296-
diagnosticsList = ScriptAnalyzer.Instance.AnalyzePath(
297-
this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path),
298-
this.recurse);
352+
diagnosticsList = ScriptAnalyzer.Instance.AnalyzePath(p, this.recurse);
299353
WriteToOutput(diagnosticsList);
300354
}
301355
}
@@ -316,6 +370,21 @@ private void WriteToOutput(IEnumerable<DiagnosticRecord> diagnosticRecords)
316370
}
317371
}
318372
}
373+
374+
private void ProcessPath()
375+
{
376+
Collection<PathInfo> paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path);
377+
processedPaths = new List<string>();
378+
foreach (PathInfo p in paths)
379+
{
380+
processedPaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path));
381+
}
382+
}
383+
384+
private bool IsFileParameterSet()
385+
{
386+
return String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase);
387+
}
319388
#endregion
320389
}
321390
}

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,15 +174,15 @@ Param(
174174
Settings Support in ScriptAnalyzer
175175
========================================
176176
Settings that describe ScriptAnalyzer rules to include/exclude based on `Severity` can be created and supplied to
177-
`Invoke-ScriptAnalyzer` using the `Setting` parameter. This enables a user to create a custom configuration for a specific environment.
177+
`Invoke-ScriptAnalyzer` using the `Setting` parameter. This enables a user to create a custom configuration for a specific environment. We support the following modes for specifying the settings file.
178178

179-
Using Settings support:
179+
## Explicit
180180

181181
The following example excludes two rules from the default set of rules and any rule
182182
that does not output an Error or Warning diagnostic record.
183183

184184
``` PowerShell
185-
# ScriptAnalyzerSettings.psd1
185+
# PSScriptAnalyzerSettings.psd1
186186
@{
187187
Severity=@('Error','Warning')
188188
ExcludeRules=@('PSAvoidUsingCmdletAliases',
@@ -199,7 +199,7 @@ Invoke-ScriptAnalyzer -Path MyScript.ps1 -Setting ScriptAnalyzerSettings.psd1
199199
The next example selects a few rules to execute instead of all the default rules.
200200

201201
``` PowerShell
202-
# ScriptAnalyzerSettings.psd1
202+
# PSScriptAnalyzerSettings.psd1
203203
@{
204204
IncludeRules=@('PSAvoidUsingPlainTextForPassword',
205205
'PSAvoidUsingConvertToSecureStringWithPlainText')
@@ -211,6 +211,15 @@ Then invoke that settings file when using:
211211
Invoke-ScriptAnalyzer -Path MyScript.ps1 -Setting ScriptAnalyzerSettings.psd1
212212
```
213213

214+
## Implicit
215+
If you place a PSScriptAnayzer settings file named `PSScriptAnalyzerSettings.psd1` in your project root, PSScriptAnalyzer will discover it if you pass the project root as the `Path` parameter.
216+
217+
```PowerShell
218+
Invoke-ScriptAnalyzer -Path "C:\path\to\project" -Recurse
219+
```
220+
221+
Note that providing settings explicitly takes higher precedence over this implicit mode. Sample settings files are provided [here](https://github.com/PowerShell/PSScriptAnalyzer/tree/master/Engine/Settings).
222+
214223
ScriptAnalyzer as a .NET library
215224
================================
216225

Tests/Engine/Settings.tests.ps1

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
if (!(Get-Module PSScriptAnalyzer))
2+
{
3+
Import-Module PSScriptAnalyzer
4+
}
5+
6+
$directory = Split-Path $MyInvocation.MyCommand.Path
7+
Describe "Settings Precedence" {
8+
$settingsTestDirectory = [System.IO.Path]::Combine($directory, "SettingsTest")
9+
$project1Root = [System.IO.Path]::Combine($settingsTestDirectory, "Project1")
10+
$project2Root = [System.IO.Path]::Combine($settingsTestDirectory, "Project2")
11+
Context "settings object is explicit" {
12+
It "runs rules from the explicit setting file" {
13+
$settingsFilepath = [System.IO.Path]::Combine($project1Root, "ExplicitSettings.psd1")
14+
$violations = Invoke-ScriptAnalyzer -Path $project1Root -Settings $settingsFilepath -Recurse
15+
$violations.Count | Should Be 1
16+
$violations[0].RuleName | Should Be "PSAvoidUsingWriteHost"
17+
}
18+
}
19+
Context "settings file is implicit" {
20+
It "runs rules from the implicit setting file" {
21+
$violations = Invoke-ScriptAnalyzer -Path $project1Root -Recurse
22+
$violations.Count | Should Be 1
23+
$violations[0].RuleName | Should Be "PSAvoidUsingCmdletAliases"
24+
}
25+
26+
It "cannot find file if not named PSScriptAnalyzerSettings.psd1" {
27+
$violations = Invoke-ScriptAnalyzer -Path $project2Root -Recurse
28+
$violations.Count | Should Be 2
29+
}
30+
}
31+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@{
2+
"IncludeRules" = @("PSAvoidUsingWriteHost")
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@{
2+
"IncludeRules" = @("PSAvoidUsingCmdletAliases")
3+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
gci
2+
Write-Host "Do not use write-host"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@{
2+
"IncludeRules" = @("PSAvoidUsingWriteHost")
3+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
gci
2+
Write-Host "Do not use write-host"

0 commit comments

Comments
 (0)