If you write PowerShell scripts or modules, you need to pay attention to this!
As part of the many improvements and updates that Microsoft has been working on for Windows 10 and PowerShell 5, they created a new module that is very important for the PowerShell ecosystem to take note of. It’s so important, that you need to pay attention to it even if you’re not using PowerShell 5 much yet.
PowerShell Script Analyzer (PSScriptAnalyzer) is a module whose intent is to provide a detailed analysis of PowerShell script and module files. This includes ps1, psm1, and psd1 files. In computer programming, this is referred to as a linting tool. A linting tool is simply a piece of software that identifies and flags suspicious usage of the software that is identified through static analysis of the source code. PowerShell Script Analyzer performs this task by analyzing whatever ps1, psm1, and psd1 files you tell it to and using a set of built-in and user-defined rules that each identify specific undesirable or suspicious usage of the PowerShell language in those files.
This module is very important because any module that is published to the PowerShell Gallery in the future will automatically be analyzed with this module.
When PowerShell Script Analyzer was originally exposed to the community, it was as a built-in module that ships with PowerShell 5.0. That was useful, however the PowerShell Script Analyzer team wanted to get more community involvement with this module. To achieve that goal, they pulled the module from the April preview of PowerShell 5.0 and made it available as open source on GitHub instead (hooray for open source!). That was a step in the right direction, but it still required PowerShell 5.0 to function, and if you’re anything like me, you still spend most of your time in PowerShell 3.0 or 4.0. Since it’s open source though, that opens up some new possibilities, so recently I forked the project on GitHub and added downlevel support for PowerShell 3.0 or later. I have a pull request to have this integrated into the main project that is currently being reviewed, so hopefully in a little while this PowerShell 3.0 support will be integrated into the main project and available on the PowerShell Gallery. In the meantime you can find my fork here, and you can download the module built from that fork here: PSScriptAnalyzer for PowerShell 3.0 and later. Now that this module includes support for PowerShell 3.0 or later, more community members can participate and provide feedback on this important module.
You should immediately start using the PSScriptAnalyzer module to perform static analysis of anything that you share with the community so that what you publish is clean.
Once you have this module on your system, you probably want to know how to use it. This module currently includes two PowerShell cmdlets: Get-ScriptAnalyzerRule and Invoke-ScriptAnalyzer. Get-ScriptAnalyzerRule will return a list of the rules that are in use by Script Analyzer to perform its analysis of your script files. At this time there are 7 informational rules, 26 warning rules, and 4 error rules. Invoke-ScriptAnalyzer will analyze one or more files that you identify using the rules that are in use by Script Analyzer, notifying you of any infractions to those rules so that you may correct them.
The final list of rules that are used in the RTM release of PSScriptAnalyzer will be decided upon based on community feedback. Additionally, how those rules function is dependent on community feedback, and the list of rules that will ultimately block a module from being published to the PowerShell Gallery will also be chosen based on community feedback.
You should provide feedback to the PSScriptAnalyzer team about the rules that it uses, identifying bugs of course, but also identifying rules that shouldn’t be rules, rules that are missing, rules that have an incorrect severity, and any other changes that should be considered regarding the rules that are used.
If you have feedback to share, you should do so on the Issues and Discussions page of the PowerShell Script Analyzer project. If you haven’t clicked on that link already, do so now. You’ll see that there are a lot of discussions that are currently ongoing, and issues that are already being raised. Given the importance of this module for the PowerShell community, if you have feedback to share, share it! The PowerShell Script Analyzer team needs it.
The PowerShell Script Analyzer team is so eager to engage with the community that they have even started hosting live update meetings to the public. These are currently planned for every three weeks, but that schedule may change as required. The next meeting is currently scheduled for Tuesday, May 26th at 2PM EDT (link to calendar invite). It would also be worthwhile to keep an eye on the Twitter accounts of PowerShell MVPs (you can find me here: @poshoholic) as well as on the PowerShell Team Blog and the PowerShell Slack channel (more on that in another post) for further announcements about these meetings.
With all of those details out of the way, you may be wondering how you can use this module. That’s a good question, because documentation hasn’t been published for this module yet. The module is very easy to use, but at first the information it provides can be very overwhelming so you should take small steps when you’re just getting started. I’m going to use the remainder of this blog post to walk through what it is like using this module. Don’t be intimidated. This module is still under development, and the details you uncover when you start using it can be a little daunting. If you’re having a hard time, reach out to the rest of the community for feedback and someone should be able to help you through it.
Remember, this module will be analyzing files that have not previously been analyzed, ever, identifying places where you might want to improve those scripts, so at first it may seem very noisy. Also keep in mind that some of the things it identifies are completely benign – this is where community feedback comes in, so that we can help steer the team towards a final set of rules that really makes sense.
As an example, I’m going to use PSScriptAnalyzer to analyze my HistoryPx module. First, I’ll change the current location to the root of my HistoryPx module (On my system I simply cd into Modules:\HistoryPx). Then once I’ve done that, I’ll run an analysis on the psm1 file, by invoking the following command:
Invoke-ScriptAnalyzer -Path .\HistoryPx.psm1
Here’s what the results of that command look like:
This shows me that according to PSScriptAnalyzer, I have 8 rules that are being broken, and each of these rules has a severity of warning. Now that I have some output, I need to review what it is telling me to see if I have anything that I actually need to change. Here’s a breakdown of what these rules are trying to tell me:
The first two warnings are based on a rule that is called PSAvoidGlobalVars. That rule simply indicates that you shouldn’t use global variables. In HistoryPx, I refer to the $Error variable, and I use $global:Error to do so. In this case, the warnings are benign, because the rule is too strict. It should actually check to see if I am using non-builtin global variables, not global variables in general. That is my opinion at least, and since it doesn’t do that right now, I’m going to open an issue with the PSScriptAnalyzer team on GitHub using the link I provided earlier (copied here). A quick search revealed no open issues related to PSAvoidGlobalVars, so I opened one (here).
While typing the rest of this blog post, the PSScriptAnalyzer saw the issue I logged and responded thanking me for reporting it and indicating they would fix it soon. This type of responsiveness makes supporting them and contributing to this module an absolute pleasure!
The third warning comes from a rule called PSAvoidUsingPositionalParameters. When you share scripts, using positional parameters is discouraged because the person reading your script may not know what parameter is being used. To address this problem, you should always use named parameters in your scripts. PSScriptAnalyzer did its job by identifying where I had left out a positional parameter, so I need to fix that. The warning indicates which file and what line number were being analyzed when the warning was found, so I simply open up HistoryPx.psm1, make the change by adding the missing “-Message” named parameter, and then move on to the next warning.
Warning number 4 indicates that I shouldn’t use the trap statement. I use the trap statement in script modules I write because it ensures that terminating errors are properly treated as terminating errors if they are raised while the module is loading, which ensures that the module does not load in a semi-functional or broken state. Therefore, this warning is benign for me and another issue I need to open with the PSScriptAnalyzer project (which I did, here).
Note: if you do come across something where you feel you need to open an issue, search the Issue page using the Filter box for the rule name and see if the issue has already been posted first. If so, contribute by adding any comments you want to share to that issue rather than opening a new issue. That will help the PSScriptAnalyzer team decide what to do with these issues once they are opened.
Moving along, warnings 5 through 7 all come from the PSAvoidUninitializedVariable rule, which identifies that I haven’t initialized a few variables before using them in my psm1 file. However, that rule only identifies when variables are initialized in the same file – it does not identify when variables are initialized elsewhere, and in this case, as with all of my modules, I initialize these variables in a snippet that this module uses, so the warning is benign. For these warnings, I can open another issue about the rule, but I’m a bit of an exception in this case. Alternatively I can suppress the warnings if they are truly benign and not considered a bug by adding a System.Diagnostics.CodeAnalysis.SuppressMessageAttribute to my script module file. For example, if I add this to the top of my psm1 file, warnings 5 through 7 go away:
This suppresses that rule in the current file for all variables. I can suppress it for a single variable by replacing the empty single quotes with a variable name in single quotes, but that only suppresses the rule for one variable. From what I can tell there is no way for me to suppress the rule for multiple variables (wildcards are not supported, and if I use multiple attributes, the last one I use wins). There are some obvious bugs/design issues with this right now, but the point is, you should be able to suppress rules that you know you can safely ignore.
Be careful when you suppress rules. If you suppress a rule, you want to suppress it in a minimalistic fashion, so that you still get the benefit of that rule in other places.
In this case, I’m not sure what the right action is, but the rule itself has issues, so I decided to open up an issue as a discussion topic on GitHub (here).
If you feel something is wrong but you don’t have a bug to log, opening a discussion on the Issue page is a great approach to take.
With that out of the way, I’m on to the last warning that was raised for my HistoryPx module: PSUseDeclaredVarsMoreThanAssignments. This warning is nothing but confusing for me. It is being raised because PowerShell Script Analyzer thinks I’m doing something wrong when I assign a script block to a property on a variable that it thinks is not initialized. That would be wrong if the variable was actually not initialized, but the warning in this case seems simply like a bug, so I’m logging it (here).
At this point, I have passed through all the warnings generated by the analysis, made one change to correct one of them, and started logging some issues to deal with the others. As development continues on PSScriptAnalyzer, this scale should tip dramatically to the point where you have more correctible warnings/errors for your scripts and much fewer issues to log. For now though, it is a work in progress, but I suspect that over the next little while you’ll see a lot of improvements to address the issues that are being identified in the community.
With one file done, I can now check another file, or run analysis recursively over my entire module. To perform recursive analysis, you would run this command from your module root folder:
Invoke-ScriptAnalyzer -Path . -Recurse
That will recursively analyze all files in your module so that you can get a high level view of everything that needs to be corrected. The goal here is simple: continually fix the issues over time so that when you run this command recursively, you only see new issues that pop up (or ideally, no issues at all). That will be the best way for you to maintain a high quality for any modules and scripts that you want to share with the community.
There are more things you can do with this module (such as create your own rules), but for now I just wanted to give you a taste of what it is like, and how the feedback process works so that you can start giving it a try and participating in the community effort to steer this in the right direction.
I hope to see you at the PowerShell Script Analyzer Community Meeting next Tuesday!