Skip to content

Discover settings file from given path #614

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 77 additions & 8 deletions Engine/Commands/InvokeScriptAnalyzerCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands
HelpUri = "http://go.microsoft.com/fwlink/?LinkId=525914")]
public class InvokeScriptAnalyzerCommand : PSCmdlet, IOutputWriter
{
#region Private variables
List<string> processedPaths;
#endregion // Private variables

#region Parameters
/// <summary>
/// Path: The path to the file or folder to invoke PSScriptAnalyzer on.
Expand Down Expand Up @@ -218,9 +222,63 @@ protected override void BeginProcessing()

string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath,
this.SessionState, recurseCustomRulePath);
if (IsFileParameterSet())
{
ProcessPath();
}

var settingFileHasErrors = false;
if (settings == null
&& processedPaths != null
&& processedPaths.Count == 1)
{
// add a directory separator character because if there is no trailing separator character, it will return the parent
var directory = processedPaths[0].TrimEnd(System.IO.Path.DirectorySeparatorChar);
if (File.Exists(directory))
{
// if given path is a file, get its directory
directory = System.IO.Path.GetDirectoryName(directory);
}

this.WriteVerbose(
String.Format(
"Settings not provided. Will look for settings file in the given path {0}.",
path));
var settingsFileAutoDiscovered = false;
if (Directory.Exists(directory))
{
// if settings are not provided explicitly, look for it in the given path
// check if pssasettings.psd1 exists
var settingsFilename = "PSScriptAnalyzerSettings.psd1";
var settingsFilepath = System.IO.Path.Combine(directory, settingsFilename);
if (File.Exists(settingsFilepath))
{
settingsFileAutoDiscovered = true;
this.WriteVerbose(
String.Format(
"Found {0} in {1}. Will use it to provide settings for this invocation.",
settingsFilename,
directory));
settingFileHasErrors = !ScriptAnalyzer.Instance.ParseProfile(settingsFilepath, this.SessionState.Path, this);
}
}

if (!ScriptAnalyzer.Instance.ParseProfile(this.settings, this.SessionState.Path, this))
if (!settingsFileAutoDiscovered)
{
this.WriteVerbose(
String.Format(
"Cannot find a settings file in the given path {0}.",
path));
}
}
else
{
settingFileHasErrors = !ScriptAnalyzer.Instance.ParseProfile(this.settings, this.SessionState.Path, this);
}

if (settingFileHasErrors)
{
this.WriteWarning("Cannot parse settings. Will abort the invocation.");
stopProcessing = true;
return;
}
Expand Down Expand Up @@ -287,15 +345,11 @@ protected override void StopProcessing()
private void ProcessInput()
{
IEnumerable<DiagnosticRecord> diagnosticsList = Enumerable.Empty<DiagnosticRecord>();
if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase))
if (IsFileParameterSet())
{
// throws Item Not Found Exception
Collection<PathInfo> paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path);
foreach (PathInfo p in paths)
foreach (var p in processedPaths)
{
diagnosticsList = ScriptAnalyzer.Instance.AnalyzePath(
this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path),
this.recurse);
diagnosticsList = ScriptAnalyzer.Instance.AnalyzePath(p, this.recurse);
WriteToOutput(diagnosticsList);
}
}
Expand All @@ -316,6 +370,21 @@ private void WriteToOutput(IEnumerable<DiagnosticRecord> diagnosticRecords)
}
}
}

private void ProcessPath()
{
Collection<PathInfo> paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path);
processedPaths = new List<string>();
foreach (PathInfo p in paths)
{
processedPaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path));
}
}

private bool IsFileParameterSet()
{
return String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase);
}
#endregion
}
}
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,15 @@ Param(
Settings Support in ScriptAnalyzer
========================================
Settings that describe ScriptAnalyzer rules to include/exclude based on `Severity` can be created and supplied to
`Invoke-ScriptAnalyzer` using the `Setting` parameter. This enables a user to create a custom configuration for a specific environment.
`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.

Using Settings support:
## Explicit

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

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

``` PowerShell
# ScriptAnalyzerSettings.psd1
# PSScriptAnalyzerSettings.psd1
@{
IncludeRules=@('PSAvoidUsingPlainTextForPassword',
'PSAvoidUsingConvertToSecureStringWithPlainText')
Expand All @@ -211,6 +211,15 @@ Then invoke that settings file when using:
Invoke-ScriptAnalyzer -Path MyScript.ps1 -Setting ScriptAnalyzerSettings.psd1
```

## Implicit
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.

```PowerShell
Invoke-ScriptAnalyzer -Path "C:\path\to\project" -Recurse
```

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).

ScriptAnalyzer as a .NET library
================================

Expand Down
31 changes: 31 additions & 0 deletions Tests/Engine/Settings.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
if (!(Get-Module PSScriptAnalyzer))
{
Import-Module PSScriptAnalyzer
}

$directory = Split-Path $MyInvocation.MyCommand.Path
Describe "Settings Precedence" {
$settingsTestDirectory = [System.IO.Path]::Combine($directory, "SettingsTest")
$project1Root = [System.IO.Path]::Combine($settingsTestDirectory, "Project1")
$project2Root = [System.IO.Path]::Combine($settingsTestDirectory, "Project2")
Context "settings object is explicit" {
It "runs rules from the explicit setting file" {
$settingsFilepath = [System.IO.Path]::Combine($project1Root, "ExplicitSettings.psd1")
$violations = Invoke-ScriptAnalyzer -Path $project1Root -Settings $settingsFilepath -Recurse
$violations.Count | Should Be 1
$violations[0].RuleName | Should Be "PSAvoidUsingWriteHost"
}
}
Context "settings file is implicit" {
It "runs rules from the implicit setting file" {
$violations = Invoke-ScriptAnalyzer -Path $project1Root -Recurse
$violations.Count | Should Be 1
$violations[0].RuleName | Should Be "PSAvoidUsingCmdletAliases"
}

It "cannot find file if not named PSScriptAnalyzerSettings.psd1" {
$violations = Invoke-ScriptAnalyzer -Path $project2Root -Recurse
$violations.Count | Should Be 2
}
}
}
3 changes: 3 additions & 0 deletions Tests/Engine/SettingsTest/Project1/ExplicitSettings.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@{
"IncludeRules" = @("PSAvoidUsingWriteHost")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@{
"IncludeRules" = @("PSAvoidUsingCmdletAliases")
}
2 changes: 2 additions & 0 deletions Tests/Engine/SettingsTest/Project1/project1.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gci
Write-Host "Do not use write-host"
3 changes: 3 additions & 0 deletions Tests/Engine/SettingsTest/Project2/RandomNameSettings.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@{
"IncludeRules" = @("PSAvoidUsingWriteHost")
}
2 changes: 2 additions & 0 deletions Tests/Engine/SettingsTest/Project2/project2.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gci
Write-Host "Do not use write-host"