A Crash Course in Writing Your Own PSScriptAnalyzer Rules
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 60 | |
Author | ||
License | CC Attribution - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/37360 (DOI) | |
Publisher | ||
Release Date | ||
Language | ||
Producer | ||
Production Year | 2018 |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
Rule of inferencePower (physics)Crash (computing)Computer fileScripting languageDemo (music)TwitterRule of inferenceCrash (computing)WebsiteLevel (video gaming)Demo (music)Scripting languageMultiplication signWeb pageBitPresentation of a groupComputer animationXML
01:16
CodeVector potentialScripting languageMathematical analysisLocal GroupRule of inferenceCrash (computing)Predicate (grammar)Installation artModule (mathematics)Descriptive statisticsCodeVector potentialMathematical analysisRule of inferenceStandard deviationGroup actionInternet service providerScripting languageComputer animation
01:57
Rule of inferenceCrash (computing)Vulnerability (computing)Abstract syntax treeData structureCodeRepresentation (politics)Network topologyComponent-based software engineeringRule of inferenceMereologyComputer fileDefault (computer science)Different (Kate Ryan album)Position operatorDemo (music)Data structureConnectivity (graph theory)CodeRepresentation (politics)Open sourceConformal mapExpert systemPerfect groupGoodness of fitMathematicsScripting languageProjective planeNetwork topologyCASE <Informatik>Variety (linguistics)AliasingMultiplication signAbstract syntax treeDisk read-and-write head1 (number)Focus (optics)Green's functionObject (grammar)Computer animation
06:00
Rule of inferenceCrash (computing)Demo (music)Parameter (computer programming)Crash (computing)Parameter (computer programming)Open setRule of inferenceObject (grammar)Demo (music)Scripting languageMultiplication signGoodness of fitCodeGraphics tabletFunctional (mathematics)Set (mathematics)Abstract syntax treeRegulärer Ausdruck <Textverarbeitung>Condition numberVisualization (computer graphics)Computer animation
07:59
Crash (computing)Rule of inferenceMenu (computing)VaporGamma functionHydraulic jumpRange (statistics)Convex hullDigitale VideotechnikMaxima and minimaParameter (computer programming)Object (grammar)String (computer science)Computer fileFluid staticsParsingSocial classRule of inferenceNetwork topologyParsingDefault (computer science)System administratorAbstract syntax treeInheritance (object-oriented programming)Descriptive statisticsDemo (music)Type theoryDifferent (Kate Ryan album)Formal languageAttribute grammarBlock (periodic table)Scripting languageStandard deviationVariable (mathematics)Functional (mathematics)Module (mathematics)Exterior algebraGoodness of fitExtension (kinesiology)Function (mathematics)Semiconductor memorySimilarity (geometry)CodePoint (geometry)Field (computer science)Line (geometry)MereologyElectronic mailing listFile formatSoftware testingPairwise comparisonComputer animation
17:07
Rule of inferenceCrash (computing)Term (mathematics)Hill differential equationDialectStrutCore dumpPointer (computer programming)MUDVacuumGame theoryCASE <Informatik>Parameter (computer programming)Entire functionBlock (periodic table)Scripting languageGoodness of fitObject (grammar)MereologyBitAttribute grammarSet (mathematics)Type theoryString (computer science)Different (Kate Ryan album)Error messageElectronic mailing listLine (geometry)Right angleFunction (mathematics)Functional (mathematics)Rule of inferenceVariable (mathematics)IntegerSubject indexingData storage deviceComputer fileBuildingEndliche ModelltheoriePoint (geometry)Binary codeInheritance (object-oriented programming)Loop (music)CodeKeyboard shortcut1 (number)Computer animation
26:16
Crash (computing)Rule of inferencePort scannerColor managementVacuumExecution unitStrutSubject indexingParameter (computer programming)Attribute grammarType theoryMessage passingAbstract syntax treeObject (grammar)CASE <Informatik>Row (database)Rule of inferenceScripting languagePosition operatorError message1 (number)Function (mathematics)Block (periodic table)Extension (kinesiology)Computer fileFlow separationFile formatLevel (video gaming)Different (Kate Ryan album)Default (computer science)Skeleton (computer programming)Variable (mathematics)PlastikkarteSpacetimeState of matterLine (geometry)Validity (statistics)NumberInformationCodeFunctional (mathematics)SubsetMultiplication signProcess (computing)Set (mathematics)
35:24
Execution unitRule of inferenceCrash (computing)Port scannerMaizeInclusion mapRange (statistics)Hash functionArrow of timeSineCloud computingClient (computing)Denial-of-service attackMenu (computing)Bit rateDynamic random-access memoryUnit testingObject (grammar)Cheat <Computerspiel>Functional (mathematics)Selectivity (electronic)Subject indexingComputer fileParameter (computer programming)Flow separationLine (geometry)Block (periodic table)Error messageSet (mathematics)Scripting languagePresentation of a groupExtension (kinesiology)Multiplication signCodeMessage passingDifferent (Kate Ryan album)InformationAbstract syntax treeInstance (computer science)Right angleDemo (music)Directory serviceSkeleton (computer programming)1 (number)Row (database)BitBuildingUniqueness quantificationSoftware frameworkFile formatFunction (mathematics)Type theoryRule of inferenceSingle-precision floating-point formatDoubling the cubeCASE <Informatik>System callPerformance appraisalTable (information)Electronic mailing listLoop (music)Wrapper (data mining)Network topology
44:33
Event horizonRule of inferenceCrash (computing)Demo (music)Parameter (computer programming)Crash (computing)Scripting languageTwitterRule of inferenceComputer animation
45:17
Coma BerenicesXML
Transcript: English(auto-generated)
00:10
All right everyone, we are currently scheduled for an on-time departure, so let's begin. Welcome to a crash course in writing your own psScript analyzer rules. My name is Thomas
00:21
Rayner, I'm a Microsoft MVP for PowerShell, an honorary scripting guy. I've got some plural site courses on Azure automation that you can check out. You may find me on Twitter at MrThomasRayner and I have a blog, workingsystemin.com, and what we're here talking about is making your own custom psScript analyzer rules. This is absolutely a crash course in doing so.
00:44
It is a bit of a 45-minute sprint, so please hold questions to the end if there's time and if there's not. Twitter is a good way to find me, find me around here. I'm the guy who looks like me. And you can download the slides, demo scripts, any of that stuff for this and my presentations on my GitHub page. So let's get atter. First, very quickly, just to do a little
01:07
level setting, what is psScript analyzer for the uninitiated? Maybe you've used it before, but what it genuinely is can be found out by installing it and examining it. If you install it from the PowerShell gallery, you can check out the description on the module,
01:21
make it a little easier to read. What it literally does is provide script analysis and checks for potential defects by applying groups of built-in or customized rules on the scripts being analyzed. So what's that actually mean? Well, there's a whole bunch of rules that come with psScript analyzer when you install it that will check your code for a whole wide variety of
01:40
different best practices and style and standards applications, and you can extend this and customize psScript analyzer to check for things that you write. And that's what we're here today. We're not so worried about the built-in rules today. We are writing our own for our own purposes. Why would you want to use psScript analyzer in general? Well, the basic rules are for the
02:03
most part pretty well-reasoned. They're in there to help you. They're mostly based on things that the community and that the PowerShell team have decided are best practices and things that they want to see people do or see people avoid. They're not all perfect as we'll discuss in a moment, but for the most part they kind of make sense. psScript analyzer is a tool that
02:25
will help you and your co-workers improve the readability of your script. So there's rules for things like not using aliases. If you use VS code and you've seen the little green squiggles and stuff under aliases that you've put in, that's psScript analyzer analyzing your code on the go saying, hey, you shouldn't use this alias. It would be more readable to use the full command name.
02:46
There are a whole bunch of rules and you can write your own to help make your scripts more secure and close vulnerabilities in them. There's default rules such as avoiding plain text passwords, although that rule is not perfect, and a variety of others.
03:01
You can use psScript analyzer to enforce conformity across your code. So if you and I are working together on a project that's open source or working on code together at our organization, my code should look like your code should look like her code should look like his code so that when I have to go maintain your code while you're on leave or on vacation or change jobs, then it looks a lot like the code that I wrote. So regardless of what
03:27
indentation schema you prefer, as long as we're all sticking to the same, we're pretty good. And you can enforce conformity rules and stuff like that. And then finally, the biggest one that we're here talking about today is flexibility. So with the custom rules, you can have this do just about anything you want.
03:44
Being able to parse the abstract syntax tree will allow you to look through every single conceivable part of your script and determine whether or not it's playing by the rules that you want it to. There's all these default rules, but you can use psScript analyzer for any of the above purposes or ones that you make up on your own.
04:04
Why would you not want to use psScript analyzer? Well, what drives people away are some of the are hotly contended among the community about, well, we should just take this one out or this one's not perfect, or this one's really difficult to get your head around or,
04:23
oh, it's so many false positives. And they're kind of built for the 80% of use cases. And a lot of the people who are active in the community run into the 20% of use cases that are false positives or not applicable. The other thing that people end up criticizing psScript analyzer for, in my experience, are that not all of the default rules contain all of the
04:46
all of the use cases are not contained by the default rules. So there's lots of gaps. There's things that I wish psScript analyzer acted this way. I wish that it would check for this. But there are ways of making it do that. We are concerned only with the second
05:01
problem today. We're not really addressing the default rules as I've mentioned. We are looking at building our own rules to try to fill some of the gaps that we perceive with the psScript analyzer. There's a little prerequisite knowledge that we need before we can actually start making our own rules. Don't worry, we're almost over the slides. The first is, what is AST? You guys were in Bruce's talk on Monday,
05:23
but for the people at home, abstract syntax tree. It's a tree representation of the structure of code. Everybody with me, right? Everybody's got it locked down. Perfect experts already. Come again. The abstract syntax tree allows us to break down PowerShell code into its structuring different components and parse the file into what's effectively an object
05:44
that we can work with in PowerShell like we're used to. Still not with me? We'll show you. The biggest thing you need to know when you're making your own psScript analyzer rules is how to work with the AST. So it's going to be a large focus on what we're here doing today.
06:00
Speaking of what we're doing today, it's demo time, and we are right on time. Here's our scenario. We have a feeling that having more than one parameter with value from pipeline set in the same parameter set is a bad thing. Regardless of how you feel about that, it's my presentation, so I'm saying it's bad, so it's bad.
06:21
The reasons for that genuinely aren't even important. We're not running any of the functions that we're actually testing today. We're literally just parsing them and looking at the abstract syntax tree. So we have two objectives in the demo that's going to make up the rest of our time together today. The first objective is to learn how to identify the
06:40
violations. Before we can report on them and before we can actually make a script analyzer rule, we need to learn how to parse a script and look through it and identify here is a condition where there are multiple parameters in the same parameter set with value from pipeline set, and we don't want to do that using regex because that sucks. The second thing we want to do
07:03
is learn the actual syntax and how to build a custom PSScript analyzer rule, because it's great to be able to identify violations, but we don't want to have to reinvent the wheel and make our own rule running engine ourselves. That's what script analyzer does. We'll learn how to identify violations, and then we'll learn how to make PSScript analyzer
07:23
work with our code for identifying violations and get it all done. Be advised, this is far from a perfectly polished solution. You signed up for a crash course by sitting down in this room. So there is lots of room for improvement, but time constraints, this would take you more than 45 minutes to write in the wild, so bear with us.
07:43
If you see stuff, you're like, oh, couldn't you have used that? Probably. But just to show you the basics and get you going and give you kind of the tools you need as a launch pad to get in and make the perfect rules you're after, this will get you there. Everybody clear? Good. So I have a few files open in my Visual Studio code.
08:04
I've imported a couple of modules already, and I'm going to clear this, so there we go. I imported the PSScript analyzer module that you already saw we can get from the PowerShell gallery, and I've imported also this AST helper module, which is a module that I wrote
08:21
that contains a couple of small functions and exists on the PowerShell gallery. You do not need this module to get started with script analyzer rules. There are lots of good alternatives out there as well. I just wrote this one, so I like it, and again, you're in my session, so you're going to see it. It comes with just a few commands. This invoke tokenize we're not even going to look at today. That was me goofing around.
08:43
There are two functions in this module that I wrote that we're actually going to take a look at and use to dig into scripts with. The first is get AST type, and I'll show you what that does in more detail, but at face value, it's going to parse a file and tell you all the different types of AST objects that make up the abstract syntax tree for that file.
09:05
The other one is get AST object, which will get you all the AST objects of a specific type in a parsed file. It'll make sense as we go through it, I promise. I've got this simple function that we can look at real quick first,
09:20
and it literally just contains these seven lines of code. It's got a test function, it takes one parameter, and this one's commented out, we'll be back later, and then it just writes output the value of that parameter. I'm not even going to run this code because it's not important here. I'm going to store the path to that in the simple variable, just so we don't have to type that whole thing out.
09:41
If we look at the definition of what this get AST type actually does, let me make a little more room. This is just a comment-based help, I won't scroll up for that. What it does is it takes one parameter and it's the path to the script that we want to parse and get all the types of AST objects for. Then what it'll do is it's basically just doing some hand-holding for some of the AST methods here. The system management automation language
10:06
parser class has a method called parse file, and what that does is it's actually, like it sounds, going to parse the file that you give it and return the abstract syntax tree for that file. It takes the path to the file you want to parse and a couple of reference
10:21
nulls, we don't worry about that, don't care. I'm going to assign that output to the AST variable. Abstract syntax trees have a method called find all that we're going to take advantage of in this thing. What it'll do is it will search through the whole tree for things that match what's in this filter script that you provided. In this case, I'm just looking for
10:45
all of the different objects in the tree. Then it takes a Boolean, it's either true or false like a Boolean, and what that does is tells it whether or not you want to recursively search through all the different script blocks that it finds in that file that you've parsed.
11:01
Then what I'm doing with that is I'm just doing for each object that's in the tree, I'm doing a get type because they're all different types of objects, and then I'm selecting the unique objects that I got. Then don't worry about the catch, I was a little lazy there. All I'm doing is parsing the file, getting all the different types of AST objects that exist there. If I run that on that simple script that we saw already,
11:25
you can see there's a whole schwack of different AST objects. There's a script block AST, parameter AST, pipeline AST, etc. There's all these different types of objects. The point that I'm highlighting here is the tree is not made up of uniform, concise,
11:41
similar objects. The objects have a lot in common with each other, but they are all distinctly different, which means a lot of digging around on your part when you're writing a rule. Let's use the get AST object command that I wrote and just look at exactly what that does before we move on. What it does is it takes two parameters. It takes the script path
12:01
just like the last one does, and then it takes the type of the AST objects that you wanted to look at. The first one is giving you all the different types of AST objects that are in that tree, and this one is giving you all the actual objects of the specific type out of that file. What we're doing first is converting the type that you give it to the actual fully qualified type so that when we do a comparison later, it'll actually work.
12:25
And then this line right below it where we're parsing the file is exactly like it was in the line before, and then we're using the find all method on that again, and this filter is a little bit different. Instead of just getting where they're all existing, we're getting where they are of the type that we're looking at,
12:43
and that'll make sense in the next example. And then for each of them, I'm just writing them out. So if we do get AST object script path simple, type command AST, we're going to get all of the command AST objects in that simple scripts abstract syntax tree.
13:01
Turns out there's one. There's one command AST object in the tree that makes up that file, and you can see it's got command elements, invocation operator, defining, extent, and parent. The specifics of this are not hugely important quite yet. If we do the parameter AST objects, which you'll recall our demo that we're doing,
13:21
our problem we're trying to solve are things that are applied to parameters, value from pipeline. We don't want that on every parameter in a parameter set, so it probably makes sense to look at the parameter AST and see what's going on there. If we look at the parameter, it is slightly similar but distinctly different from the command AST. Parameter ASTs have attributes, they have a name, they have a default value,
13:43
static type, whereas the command AST had a lot of different attributes to it. They also have an extent and a parent just like the command AST did, but you can see they're different. They're different objects even though they're in the same tree for the same file. So if I want to poke around with that some more, I'm just going to assign the value to
14:02
a variable called param, and just like that you can see that's what's in there, and so you might go, okay, so really easily I should be able to, if I just want to retrieve the name of the parameter, maybe I'm writing a rule that makes sure that I capitalize the first letter of the parameter name, because that's my standard. So I should be able to isolate the name attribute of that parameter and check to see
14:23
the first characters capitalized, right? Name presents itself as a string. Well, the next line is kind of giving it away. You'd be wrong if you thought that. And you'll find that with just about every attribute in an AST object is it may represent itself as a string when you write it out, but dig deeper because they're usually not.
14:42
And so as we expand the name, it's a whole bunch of other stuff. It's got a variable path, whether or not the thing is splatted, the static type, and the extent and parent, everything pretty much has an extent and a parent. So you go, okay, great. Well, all right, well, variable path, that's what I want, and you would be wrong again.
15:01
There's user path. Is it global? Is it local? What scope is it in? Is it unqualified, etc.? Is it a variable at all? Is it drive qualified, etc.? What drive is it in? So you can see. And then finally, if we go and expand the user path, this is actually a string. We had to go that deep just to find the name of the parameter
15:20
of this parameter AST object. So it can be a little deceiving because if you just looked at it on the format list output of when we run this, oh, well, I can just isolate the name and work with it. No, dig deeper because it's not always that easy. And so if we look at it again, what I want to explore quickly before we move on is this extent that's part of every AST object.
15:43
And if we expand that, what this is, is it's a description of the code that makes up the AST object. So we have a parameter AST object. There's code in our file that makes up that object. So what file is it in? That's the first attribute. Where is it in the script? What column,
16:01
row, line, where does it begin and end, etc.? And it will tell you exactly where it is. And you'll notice this extent also has a text field and gives you the exact text that makes up that AST object. And that's going to be useful to us as we move on later into some of the other demo. So, and if I change this simple function, I'm just going to comment out this
16:24
and make it so that I'm applying the string type and the making this parameter mandatory to this first parameter. That's all I'm doing there. And then we're going to take a look at how that changes the object really quickly. And so I'm going to have to refresh what's in that variable because it's stored in memory. And now if we look at it again, it looks very
16:44
similar, but now these attributes are filled in. There's attributes in there. Do you think those are just an array of strings? You should know better by now, if we go in and we look, they're not. They're not even the same themselves. The attributes that are assigned to it, the string is here, type name string, and then type name parameter. And the argument that's
17:04
passed is mandatory. And so we're slightly, we're slowly starting to dig in here and keep in mind where value from pipeline is applied. It's applied inside of one of these parameter types. So if we isolate the named arguments from both of these attributes, well, the string doesn't have one. So if we do that, we just get the mandatory named argument out of the parameter
17:25
attribute applied to our first parameter parameter. It's getting a little deep and convoluted, but stay with me because this is how it works. And so if we switch to a more complicated file, this is one that we're actually going to see some violations to the rule we're building in.
17:42
It's again, a very arbitrary function. It doesn't do anything particularly useful, but it's going to highlight some of the use cases for the rule we're building pretty neatly. This is a function called get something has command a binary parameter block, and it's got six parameters. The first parameter mandatory is set to false, and it's got value from pipeline.
18:02
You don't have to specify equals true. We're just specifying value from pipeline will make it true. Similarly, param two also has value from pipeline, but it's explicitly equal to true here. So we have a violation. We have a problem. This script is going to violate the rule that
18:20
we're going to build. And then we have param three. Oh, a different use case. It's present, but it's equal to false. So this particular parameter is not part of the problem because we're explicitly saying it's false, but it is present in this whole param block. Param four value from pipeline is also false. Param five has it as well, but it's in all lower
18:44
case. Maybe that'll be a monkey wrench later and param six doesn't have it at all. So this one again is also not part of the problem. It really does nothing of consequence. It writes the output in this one little right host here. And then inside of this, just to highlight another use case, we have a nested function called get something else.
19:02
These two parameters, other param one and other param two are part of the same parameter set. Their parameter set name is equal to one and other param. And so this is a problem because you'll see value from pipeline is set here and value from pipeline equals true is set here. So we have two parameters in the same parameter set that have value from pipeline set. These last
19:24
three other param three, four, and five are all in parameter set two and only param other param five has value from pipeline set four and three both have it set explicitly to false. So they're not problems. So in this script, there should be two violations. This one where they're both
19:46
in the param block and there is no explicit parameter set labeled. Good. This is the script we're working with for most of the rest of this. So if we go back here and I'm just going to set the path to that equal to a variable name complicated. And if I invoke script analyzer
20:02
on this file, as it is right now, I get one warning and it's the rule name PS avoid using right host. It's a warning in the script name, more complicated up PS one in the line. And it tells me it's using right host and shouldn't do that. You don't think it kills puppets, et cetera. And so, and if we go and look at this again, line 26 in this file,
20:27
right host, there it is. And so regardless of whether or not it actually belongs there, it is giving me a warning on that. So what it didn't do was anything related to the rule that I actually care about. Hello, we've identified a gap in the PS script analyzer rules,
20:42
and we want to detect value from pipeline. So let us keep digging. How do we actually identify that error case in that situation where we have violations? It's great to scroll through it and point and go, that's broken. How do we actually do it in code? So we'll start, like we started looking at the simple file by getting all the different types of AST objects
21:01
that are in it. And there's a few more than there were, but there's a lot of familiar things in here already. Function definition, AST, parameter, block, AST, parameter, attribute AST, because the parameter attribute is applied to the parameter. So maybe we want to start looking at those types of objects and we can look at just the first one of those.
21:22
And well, the first attribute in this whole thing is actually commandlet binding and it's applied to the param block. Well, and it's also applied to every single parameter that we have, but you know what? That might be a little too narrow for our focus because we'd really like to loop through the parameters themselves so that we can report on which
21:40
parameters are having problems. So we can look at the param block AST and just look at the entire block. And you'll notice as we scroll up here, it has that attribute, the commandlet binding attribute is applied to it. It's got parameters that are inside of it. These are probably parameter AST objects that are in here. It's got an extent, it's got a parent. That might be
22:04
useful for us later, but a little too broad. Attribute AST was a little too narrow. This is a little too broad. We can look at the entire function definition AST to see what's in there and see that it's a completely different type of object than what we've already looked at. It's not a filter or a workflow. It's got a name that I bet is more complicated than it's
22:23
being presented here. And again, too broad. But parameter AST, it's the Goldilocks rule. This one should be just right. So if we look at that and we just look at the first one, we see it's param one. We see the attributes. We already looked at the simple scripts parameters. So this should be a little bit familiar here. And that's probably what we're going to continue
22:44
digging around in and poking at to see what is going on. So I'll use the get AST object command to get all of the parameter AST objects out of this file and store them in the variable params because I'm creative. And if we just write out what's in this variable, it scroll
23:01
by really fast. You caught that all right. But you can tell by the last one, other param five is the last parameter in the entire array of parameters in that file. So we got, if you don't believe me, we'll scroll up. There's all of the starts of param one, which was the first param in the first function. And it ends at other param five,
23:21
which is the last parameter in the nested function. So that's what we got there. To make this a little easier, let's just look at the first parameter so we can work with that and just work on identifying whether or not it's a candidate for violating our rules. Does it have value from pipeline? What is it set to? So if we just look at that again,
23:41
boom, parameter AST object, we saw this before we've isolated it here. And we already have a pretty good idea that we want to look at the attributes on that parameter because we want to look at the parameter attributes. See a value from pipeline is an argument set there and we can see it is indeed where it's got the string attribute and it's got the parameter attribute. And again, we're just going to isolate the named arguments on it here because
24:04
we know that those are the in the parameter attribute and we get the argument named mandatory. The argument is false and the argument name value from pipeline and the actual argument is value from pipeline. If we go back and look at our script here, let me just go down a couple
24:21
lines. Here we are in other, sorry, in param one in the parameter attribute, we have mandatory attribute is equal to false and then value from pipeline didn't get an explicit value so it's just equal to the value from pipeline. So maybe we can just trim out the argument names.
24:42
So we see that it's got mandatory, it's got value from pipeline, boom, there's an array. Does that array contain value from pipeline? Maybe this is a candidate for that. The problem with doing it this way is we're not quite off the hook yet. mandatory is in this list but it's set explicitly to false. So it's not enough just to see that they're in here,
25:02
we have to see that they're in here and see what their value is set to. Specifically we have to make sure that they're in there and the value isn't equal to false. So what we can do is, is value from pipeline even present? So we'll dig back into our parameter and we're going to look at the attributes and the named arguments and the argument name
25:20
which was this little array here and we're just going to convert the model lower case to take care of that weird one where the capitalization was weird and we're just looking for the index of value from pipeline to see if it's present and its index is one because array started zero and mandatory is zero and value from pipeline is one so it's in there. If we look at the one
25:42
in the five slot that's param six and you'll recall that one didn't have value from pipeline in it at all. So if we run the same code on this other parameter we'll see that it returns negative one. So knowing how this index of method works we can reason that if value from pipeline is in
26:01
any way associated with this parameter this command will return an integer greater or equal to zero and if it's not it'll return negative one or less than zero. And so that will isolate the we need to look at it closers to we don't need to look at this ones anymore just to see if they're there or not. If we look at the parameter in the sixth slot that's other param one
26:24
we can do the same thing with parameter set name because remember if they're in different parameter sets they're not going to be called at the same time so we don't worry about that so much for this. And we can see that the parameter set name is in the two slot of this attribute mandatory is in the zero value from pipelines in one other parameter set name is in two
26:41
good. So we're left with three scenarios here if we look at this complicated script and they're captured in the first three of these. We're not worried about param six because we've already figured out how to ignore those ones but we're worried about param one where it's there but not explicitly set to anything but it's there which means it is true. We're worried about these ones
27:04
where they're there and they're equal to true and we need to remove what could be a false positive of these value from pipelines that are present but equal to false so that's what we have to actually identify and look at. So if we look at our first param and we look at the named
27:21
arguments we see that it's got mandatory equals false and value from pipeline is value from pipeline that's our first use case. And if I get the index for that one and I just store it in the value from pipeline index variable I can go back and get the named argument that's at this spot in the array and I can see that I've isolated the value from pipeline
27:40
argument the value from pipeline argument in that attribute and I can look at it and see what its actual argument is to see if it's false or true or whatever. Yeah I could do this with some where objects stuff I could do this a whole bunch of different ways but you'll see why I'm doing it this way as we kind of carry on. And if we look at the argument that's actually passed
28:00
this is the argument here the argument is value from pipeline but of course it's more complicated than that. The value is actually true it's a boolean but if we look at the extent we can see that and if we look at the text that makes up the extent we can see that the actual argument being passed is value from pipeline. As we look at the next parameter
28:21
and just do the exact same thing see where in the array of attributes the value from pipeline is make sure we isolated that one specifically on its own and look at the argument that it's passed we can just skip a step here and see what the actual text is. It's equal to true. So we found a parameter we found that it has value from pipeline and we've identified both of our true
28:43
cases where the value is just value from pipeline and where the value is true because that means that we've actually got this thing applied in both cases. Here's one where it's present but it's equal to false this is the third example here and we'll isolate where it is by getting the index of it and we can see the argument name value from pipeline the argument
29:03
itself is false so it's not present it's not a problem and if we just look at the text that makes that up it's false. So we know that the first parameter we looked at is a problem because it's there we know that the second parameter we looked at is a problem because it's there and it's equal to true and we know that the third one is not a problem because it's
29:22
there but it's equal to false and using this value from pipeline index type of variable if it's negative one it means it's not present we don't have to worry about that and so if we do the same thing for parameter set name I'll just do this real quickly and show you that we successfully isolated that parameter set name in the other param one the next one in the list
29:44
and if we look at that argument itself this is a completely different type of attribute and it's single quoted value is one etc we could probably use this value but we can also just use the text that makes up that actual piece of code and isolate it as one and what this means
30:01
I'm just going to get the same for the value from pipeline there is we can build ourselves a little custom object and what it does I'll just run this line and show you we can go through all of the parameters that are in the file and strip out and make ourselves very easily comparable
30:22
objects that contain the parameter name the value from pipeline whether it's true or false or value from pipeline or whatever it might be and the name of the parameter set that it's in and then what we can do is take these if we go through all of the parameters and make a little object like this that's really easy to work with we don't have to do as much messing around with
30:42
the really complicated ast objects and so we can go through and say all of the parameter so everything in this little array that is in parameter set one is there more than one that has value from pipeline in a true state then we can report what those ones are and so what I did here was I just made a custom object based off of the name of the parameter using our big
31:03
easy to remember path to that attribute and then the exact same thing that we did above we got the value for value from pipeline and the value for the parameter set name how are we doing here we're doing okay maybe now we can make this into a ps script analyzer rule we've done a pretty okay job of analyzing the uh the problem so every ps script analyzer rule
31:28
that's written in powershell and not c sharp is in a psm one file and within there they're in a function you can name the function whatever you'd like I've named this one this is just the skeleton of what most of them all look like as deny value from pipeline have commandlet binding
31:45
have an output type these actually output diagnostic record objects but I'm lazy so it puts out an array of ps custom objects and ps script analyzer can figure out if they're valid or not and then it takes one parameter it is a mandatory parameter that I am validating not
32:03
all are empty because why just wear your belt when you can wear your belt and your suspenders and it takes that parameter is a script block ast because that's what ps script analyzer is going to give our rule it's going to give it a script block ast object into a script block ast
32:20
parameter that's the name of the parameter for the very intuitively named script block ast for a script block ast object then inside of a process block because we're taking these things in from the pipe maybe I like to put it inside of a trie just because well I'm easier and the ps script analyzer will suppress a lot of your errors
32:41
you got it first identify all the violations so you need to compile the code and write it and put it in there to actually find where your violations are and we'll come back to that in a second because we sort of covered that we just haven't put it all together yet and then what it'll do is write out one of these diagnostic records or as I've lazily labeled it a ps
33:01
custom object excuse me which contains at least these four attributes the first is a message this is a very valuable message just in my skeleton so I'll go back and replace that with something a little better the extent remember those extents it tells you exactly where in the code the piece of or where exactly in the file the piece of code we're looking at is
33:24
the name of the rule that gave us a violation and the severity level whether it's informational warning or error and then just as I have it here this will get written out into the pipeline and displayed and formatted in ps script analyzer so if we take the path to that custom rule
33:44
this is a valid rule and go invoke script I type so good in public we're going to do it on the complicated script that we've been looking at and how do I use my custom rule well luckily for
34:00
us there's a custom rule path parameter and I just put it in there in quotes because it got spaces and stuff in the name this will only run the custom rules that I give it and I can give it a folder and I can give it a wild card and I can run a whole bunch of different rules but this will only run the rule that I give it if I want it to run all of the default rules or a specific
34:22
subset of the default rules and my custom one I need to do something like include default rules because otherwise it'll go you clearly just care about your custom rule because you didn't explicitly put in all the default ones but because we're here to learn about custom rules I'll just run that and I can see that I got three errors and they're all very valuable messages
34:46
but you can look at the object that we wrote out and you can see how it forms what ps script analyzer actually gives you it gives you the rule name which was my invocation which was the name of the file and the name of the function inside of it so I like it in
35:01
this format for my custom rules because maybe I have more than one in the ps1 or whatever the severity was a warning and then it's got a script name and a line number well I didn't give it that I gave it an extent instead and that extent was used to format this output and tell you what file it's in and what line number it's on and then our message if we look at one
35:21
of these things if we look at one of those we can see they're more complicated than the format table output let's on here's line one column one message very valuable message this is all information the extent object is in there as well as well as the rule name the severity
35:43
if I had a rule suppression id I could I could have seen that in there as well and suggested corrections or something that are awesome to put in there when you're using vs code and it the little light bulb pops up and says you should change select to select dash object and you go no I like it the way it is no when you go yeah okay I'll do that and you click the light
36:02
bulb and it expands it it's because it's using that suggested correction we don't have time today to talk about suggested corrections but I highly recommend looking into them because they're neat so that is the skeleton that's the framework that we're building around we are receiving the script block ast and then we are doing our voodoo to identify our violations and we are going to
36:24
write out diagnostic records errors instances of the violation so that script analyzer can do its little magic at the end and format them nicely so just like a cooking show I've got something I prepared earlier and deny value from pipeline dot psm one tay is a psm one just like the
36:43
skeleton we looked at and you're going to start seeing the stuff that we were doing in the line by line execution and the stuff we were doing in the skeleton start to kind of merge together we've identified the violations we've looked at the formats and now let's marry the two together and get this thing done so the function deny value from pipeline has commandlet binding
37:02
it has an output type of ps custom object because I'm lazy it's diagnostic record we all know that it takes one parameter that's mandatory and it is script block ast which is a script block ast object just like the skeleton we just saw all this it has a process block and now we get into identifying the violation we kind of already did that but let's put a nice bow on it these two
37:24
lines are doing what get ast object does that one little wrapper function that we saw kind of the beginning of the demo it's going through the ast that it's been passed so we don't need to parse the script because ps script analyzer already did that but so we received the as the abstract syntax tree and we're going to find all of the first I'm getting all of the
37:44
param blocks that are in there and you'll see why I'm doing that as we write out the errors and I do not want to go recursively through all of the different script blocks that I'm being passed I just want to look at the one that ps script analyzer gave me because ps script analyzer will give me all of the individual script blocks so I don't need to go and dig deeper
38:01
then on line 13 I'm doing the exact same thing except instead of param blocks I'm getting the parameters themselves that makes more sense because that's what we're going to loop through here so I'm going to make an array list because I attended the performance related session yesterday and I know that that's better than doing plus equals all the time and I'm just naming
38:20
it param eval and that is going to hold all of those little custom objects that I kind of created for the sake of going through them all in a neat little bow and allow me to kind of do my evaluation that way so for each of the parameters that we got out of the script block that we're looking at this looks familiar right I'm getting the index of value from pipeline and if it's in
38:44
there if it's greater or equal to zero I'm storing the actual argument that it is so it's either going to be value from pipeline true or false if it if value from pipeline index is less than zero then I'm cheating and I'm just saying it's dollar false because it's implicitly false by not being included I'm going to do the exact same thing for the parameter set name
39:04
which is doing the get index there and if it's greater or equal to zero I know that there's an actual parameter set name applied there so I'm going to store the text of it and if it's not there I'm just going to use no param set name to denote that it does not have a parameter set name attached to it and then I'm adding myself a little object this looks familiar
39:25
I'm adding the name of the parameter the value from the value from pipeline if it's present and if it's not present it's just false and I'm adding the parameter set name value which is either the name of the parameter set or no param set and then almost done here I'm gonna go
39:44
get all the unique parameter sets because I've gone through all the parameters I've made my nice little easy to look at objects and all of the parameter sets are going to be listed individually there for each of the individual parameters so get all the unique parameter sets so that I can go through each parameter set in this script block because what I want to look at is to make
40:04
sure that no two parameters in a parameter set have value from pipeline set staying with me here so all of the parameters in this parameter set can be found by going all of the little custom objects that we made where the parameter set name is equal to the parameter set that we're looking at
40:23
and then we're going to get for all of the parameters that are in that parameter set all of the ones where the value from pipeline value is not equal to false so ideally this should be zero or one equal to false there or not equal to false so if it's greater than one that means
40:42
that I have more than one parameter that has value from pipeline in the same parameter set that is not equal to false and so I'll write my object out I'll write my message there were however many parameters in the same parameter set name of the parameter set with value from pipeline specified is true shouldn't have more than one because I said so the offending parameters are these
41:03
parameters that we're actually looking at here's the extent and I'm using the extent of the param block that's why I retrieved it earlier because this is a scenario where I'm making a design call there in the first param block I had parameters one two and five that all had this
41:21
thing set do so do I use the extent for the first one the extent for the second one I can't use the extent for the second and third one so I'm just gonna use the extent for the param block and say go look at the param block you've got messed up parameters in here uh the name of the rule and the severity I've decided is a warning and that is our rule whoop just like it
41:41
you want to see if it works I do so we'll just run it's in the same directory because I'm so forward thinking deny value from pipeline okay so we've identified violations
42:01
we've identified two violations because we have two instances where this thing's broken let's look at them uh very quickly because we're running low on time uh so the rule name deny value from pipeline that's the file deny value from pipeline that's also the name of the function it's a warning here is where it is in the extent we'll look at that if we can and then the message there were three parameters in the same parameter set no parameters that name because it didn't have
42:23
a name where value from pipeline is specified as tree you shouldn't have more than one the offending ones are param one two and five so if we look at more complicated here we can see param one two and five had value from pipeline set and if we just scroll
42:40
up to make that easier to look at on line three this is where the param block begins so I'm just telling you hey go look at this param block because things are jacked up this one is true this one is explicitly true and these ones are false so we don't worry about them you did find there this one's also true and this one you didn't mention it so that's fine and then on line 30 which come on is this param block we have this is the trickier one
43:07
where there are two parameters in the same parameter set name so parameter set one where value from pipeline was true they were other param one another param two so we got the names of them there and then notice that's where our output ends because these three are in a different
43:23
parameter set and there's only one in this parameter set by other param five that had value from pipeline set is true these other two were false so this one wasn't a violation and we did not get a false positive on it and so again I mentioned this is not a perfect solution by any
43:44
stretch of the imagination some ideas for improvement what if the same parameter set name exists in multiple scopes am I going to capture this more than once or am I going to have trouble with it in this scenario I had three distinct parameter set names of no parameter set name one and two we would have to do a little bit more digging to make sure that I was
44:02
capturing appropriately all my use cases unit tests would be good right I should probably make some tests and make this thing actually able to be tested actively I need a lot more use cases because there's a whole lot more ways you can screw this up and what like for instance what are the parameter set names are in double quotes versus single quotes you saw that right
44:24
here the single quotes are included if they were double quotes would they have been identified as different parameter sets again 45 minute session we didn't really have time to get too crazy with that but that ladies and gentlemen is a crash course in building your own ps script analyzer rules you have everything you need to know if we look real quick we identified how
44:44
to violate the or how to identify the violations to violate the violations we learned how to identify those we learned the syntax for doing the ps script analyzer rules it's not perfectly polished but now you have everything you need to make your own and so I would love to hear from you if you make any of your own rules if you want to talk or you need help hit me up on twitter
45:03
otherwise thank you guys very much for coming it's been a distinct pleasure and a privilege to be here in front of you today I hope you enjoyed the session and enjoy the rest of the conference.