Add Tab Completion to your PowerShell Parameters Today!
576 views
Aug 21, 2024
Enhance your PowerShell scripts and functions by adding parameter tab completion! This video goes over 5 different ways to level up on parameter tab completions. ? Get the Code Here! ? https://github.com/JeffBrownTech/jeffbrowntech_youtube/blob/main/2024-07-29-PowerShellTabCompletion/PowerShellTabCompletion.ps1 ? Read More About This ? PowerShell Tab Completion: The Ultimate Guide https://jeffbrown.tech/powershell-tab-completion/ ⏰ Chapters ⏰ 00:00 Introduction 00:22 ValidateSet 02:43 ArgumentCompletions 03:52 Classes 06:18 ArgumentCompleter 18:04 Register-ArgumentCompleter 20:42 Summary Social Links
View Video Transcript
0:00
What's up everyone
0:01
Today we're going to talk about how to add tab completion to your PowerShell parameters
0:06
A couple of methods we've already seen previously in other videos I've done, but we'll go over
0:10
some other methods as well. As always, this code is available out in my GitHub and I'll include
0:15
a link to that down in the video description. My name's Jeff. This is Jeff Brown Tech. Let's go ahead and
0:21
get started. The first method we're going to take a look at is the validate set attribute
0:26
on your parameter definition. Here we have a function defined as new widget, and we have two parameters that are mandatory
0:34
color and size. And on line's 6 and 11, we use the validate set attribute, and we put in a list of values that
0:42
are valid to use for that parameter. For color, we have red, green, and yellow
0:47
And for size, we have small, medium, and large. And then our function is just going to output what size of widget we picked and the color
0:54
of it. We'll go ahead and highlight our function here, bring it into our current session. If I run
1:00
new widget and I say size, when I get to this point here, if I start hitting tab completion
1:08
it's just going through our small, medium, and large. And if I go to color and start hitting
1:13
tab, it's going to go through red, green, and yellow only because those are the only options we can
1:19
use inside of our parameter here. If I enter, it's just going to output which options I selected
1:26
This one's pretty straightforward if you just want to have specific values set for
1:30
your parameters that you want your users to use when they use your script or function. One interesting component of Validate Set is it continues to perform that validation
1:39
outside of the parameter assignment, even if you go to try to assign a different value to
1:45
that parameter value later on. For example, this is our exact same function here
1:50
We have output of what we selected on line 31, but then on line 32, we have a dollar sign color, which is the name of one of our parameters, and we try to set it to blue
1:59
That is not in our validate set, and this will result in an error
2:05
So let me go ahead and bring this option into here. Go ahead and clear the screen
2:10
Let me bring back up our previous command we ran for a small red widget
2:14
And if we run this, we can see our output, creating a small widget of color red, but then we get an error here saying this variable cannot be validated because the value blue is not valid value for the color variable
2:28
Very interesting. I did not know this about Validate Set when I started looking at this a while back, but just be aware of that in case you run into that later in your script or function, if you try to set that to a value that's not in your Validate Set options
2:41
Along that same line, we now have argument completions. This is very similar to Validate Set, but in my notes here you can see, it does not perform
2:50
validation on the parameter value if it's not in the value list
2:54
This is just more a list of suggested values rather than a hard and fast rule like we saw
2:59
with Validate Set. Again, we have our new widget function, we have color and size, and we have our argument
3:05
completions here, red-green yellow, small, medium, large, just like we had before
3:10
But as you will see, these are not hard and fast ones that we have to provide for a command
3:16
So we'll bring in our new version of the function here. I'll say color
3:22
Now it will go through red, green, blue, yellow, or sorry, just red, green, and yellow
3:26
But I could come in here and say blue. We'll put in size, and it goes through small, medium, and large, but maybe I want to have
3:35
extra large. It will accept those just fine. Again, argument completions are just more of a suggested value
3:43
and maybe it can help prompt your user when using the function on what they should be putting in there
3:47
But it's not going to error out if they give a value that's not in your argument completions
3:51
Now with our first two methods that we took a look at there, we had a static list of values that were valid for our parameter tab completion
4:01
But another option is you can use classes to dynamically generate values
4:05
for your validate set at the function runtime. On 960, we start defining a class here called log file name
4:14
We have a dot net type here. We're going to get valid values in specific log file paths
4:22
The goal of my function that I want to have here is when I hit tab completion on my path
4:27
for a log file, I want it to give me the log files that exist in certain areas on my local system
4:34
On line 62, we're defining our log file paths in C logs and C temp logs
4:39
going to go through each of those file paths, we're going to test it, make sure it's valid
4:44
and then we're going to get the child items of each of those that are files and return the names
4:49
of each of those files. For example, let's go ahead and run these two here. We're going to see
4:55
what's currently in each of these folders. You can see we have NC Logs. I have Logs 1, 2, and 3
5:03
And in C temp logs, I have temp logs, 1, 2, and 3. The class log file name is just going
5:09
through and dynamically generating all the log files that exist in each of these paths Now that we have our class defined we going to add it to our validate set attribute We going to have our parameter definition here a file name
5:24
We have validate set on 978, and we're adding in the class name, log file name that we defined earlier
5:30
And then we're just going to output which log file we chose. So actually I need to go back up here. Let's bring this into our current session
5:38
our class definition here, and let's go into function. and bring this in
5:46
So what we'll do here is run Git log file, our file name
5:51
And as I start to hit tab here, it's already dynamically generated, all the files that exist in those two paths
5:57
And as I hit tab, we can see it's going through logs 1, 2, and 3
6:02
temp logs 1, 2, and 3, and then it'll just cycle back through over there. So classes are a great way to dynamically generate values for your parameters at runtime
6:12
instead of having static ones like we saw with argument completers or validate set
6:18
Now our next one is argument completer. This is not to be confused with argument completions that we took a look at earlier
6:26
These help add tab completion values to specific parameters and what you do is you define an argument
6:33
completer attribute for each parameter that you want to have tab completion for along with a script
6:39
block that determines its possible values. This method is definitely the most complex
6:44
but as you'll see in the example, has a lot of great power associated with it
6:49
And before we dive into a lot of these details, let's actually go look at the function we're going to add this to
6:55
So if we scroll down a little bit here, I have a function name, get fruit variety
7:00
What we're going to do is pass in a parameter called fruit
7:04
and we have three fruits here, apple, banana, and orange. And then we want to choose a variety
7:09
And we want to choose a variety that is only associated with that particular fruit
7:13
So it might be a grainy smith apple or a different type of banana
7:18
or a navel orange or something like that. The idea is when I run Get Fruit Variety
7:23
and I specify a fruit, I want variety to only return varieties
7:27
that are specific to that fruit. Hopefully that'll make a little more sense here in a second
7:31
But let's scroll back up and talk about how we start defining this. So this is the syntax for your argument
7:37
completer function. We're passing in parameters, There's command name, parameter name, word to complete, command AST, and fake bound parameters
7:47
And then you calculate your tab completed values within the function there
7:52
Those parameters we're just taking look at. Command name, which is position zero
7:56
That's the name of the PowerShell command the script block provides to the tab completion values
8:02
Our parameter name is the parameter requiring tab completion values. Our word to complete is the partial word value for a parameter that the user may have started typing
8:14
but if they didn't type anything, it will just be blank. But if they did start typing something, we can use it to match a specific value
8:21
Command AST, you really don't do anything with this. I'm actually not sure what it is anymore
8:26
That's the abstract syntax tree, but we'll just move on from that. Fake bound parameters, and these are a hash table that contain the commandlets
8:35
PS bound parameters value before the user started pressing tab. It's really just all the parameters that the person has specified so far
8:43
when they're trying to run that particular function or script. Let's scroll down and take a look at our function for our variety argument completer
8:52
At this point, we've specified a fruit parameter and a value, and we want to only bring in specific variety values based on that type of fruit
9:02
We have our function to find variety argument completer. We have our default parameters there
9:07
And then we have, on line 130, we're defining possible values. We have a hash table here and a key of apple
9:14
So if they specified an apple fruit, we have three different varieties. The same thing for banana and orange
9:21
Now we have to have the logic of which of one of these are we going to return
9:24
On line 136, we have an if statement. We're taking a look at the parameters that are currently being used on our function
9:31
If it contains fruit, meaning we've already defined a fruit when we're calling our function
9:37
we're going to look at the values associated with that impossible values
9:42
So let's say fruit equals apple. Then we're going to use where object to basically output those to the pipeline and return those
9:52
And there is an additional like statement here saying we're only going to return ones that are
9:58
matching the word to complete. So let's say I ran get fruit variety, I said the fruit is an apple, and I do dash variety as a parameter, and I type in G
10:10
Well, G only matches Gala or gala. It could also match Granny Smith
10:16
So it will only return each of those. Now, if I happen to type GR, it will only return Granny Smith
10:23
That's what this line 138 there is doing. It's only returning ones
10:27
if you happen to start typing something. If you don't type anything, then it going to return all of them in this case And then we have an L statement on line 141 This is saying if you haven to find fruit yet when you running the Get Fruit Variety function
10:42
it's going to return all of these possible options because it doesn't know which one we need to return yet
10:50
I know this is pretty complicated, but hopefully we can go through the example here
10:55
and it'll start to make a little more sense. So let me add in our variety argument completer function here
11:00
Let's go down to our Git Fruit Variety. We're going to use that here on line 154, passing in our arguments when we've called fruit variety
11:11
So we'll go ahead and bring this in here. Let's go ahead and bring this and clear the screen
11:18
What we're doing is we're going to run Get Fruit Variety. We're going to say Fruit
11:24
Hit A and it's going to give me Apple. It gave me Apple because that matched what was in Validate Set here
11:30
And then as I hit Dash Variety here, if I start hitting tab, it's going to give me just the three values for Apple, which is Gala, Honeycrisp, and Granny Smith, which matches up what we have inside of here
11:45
And again, and the other part of that, like I was mentioning here on line 138, if I happen to just type G and start hitting tab, it's only going to return the two that start with G
11:56
So that's where that like statement comes in. It's taking what you may have started typing and only returning ones that match that
12:05
Otherwise, if we just go back here and leave a blank and we start hitting tab, it's returning all three of them
12:10
But if I happen to type GR, it's only returning Granny Smith
12:15
Now, unfortunately, there are a couple of things wrong that we have with this current implementation
12:21
Let's back up. If I run get fruit variety and I actually specify a variety first, let's say Mandarin
12:28
and then I go to fruit. If I start tabbing here, it's going to give me all three fruits that I have available to me
12:36
And I could inadvertently say a Mandarin banana, which is not a type of banana as far as I know
12:42
There's an issue there. If you specify variety first, you don't get the correct fruits that are associated with that variety
12:48
The other thing we have going on here that is a problem is if I specify variety
12:55
and let's say I start typing lady finger like that, and I hit tab
13:00
it doesn't auto-complete to ladyfinger, it starts going through my full list
13:05
So what I want there is it to start filtering is if I start typing something
13:09
let's find the matching ones for that one for my varieties. We have a couple of fixes for that
13:17
Let's scroll down here. First, in the variety argument completer, this is what we saw earlier
13:24
Let's go back through this real quick. We're running if, if the parameters used so far contains fruit, then we're going to find
13:31
the matching varieties. However, if we haven't specified fruit yet and we're starting with variety, we're going
13:38
to go through each of those, but we want to only match on the ones that we type so far
13:43
So again, if I start typing G, it's only going to give me Gallagrani Smith
13:47
If I start typing navel, it's only going to give me once that match in or in a or whatever, I start there
13:54
It'll give us the correct tab completion based on what we started typing so far for variety
14:01
So let me go ahead and bring this in here. And let me bring in this again, just in case I need to update it as well
14:11
So let's run Get Fruit Variety, my variety. And if I start typing Mandarin and hit tab, it now completes out to Mandarin instead of cycling through every variety
14:25
And as we just saw there, since I only typed in M-A-N, that could match Mandarin or Manzano there
14:31
And it gives me both of them because we don't know what fruit we're dealing with yet. But I can sit there and tab between those two, and it's happy
14:38
That takes care of the first issue. The next issue is we want to return only fruits that match varieties that we specify
14:46
if we haven't to specify a variety first. Let's scroll back down here
14:52
We have a variety argument completer. So now we have a fruit argument completer. We've got all of our definitions here
14:59
We have our possible values. What we're looking at here on our if statement is, let's say our bound parameters contains
15:06
variety when we specified a variety first but we haven't specified a fruit parameter yet so it's null or
15:12
empty we're going to create a fruit matching array then we're going to iterate through each object
15:20
in our possible values table here for example the first one would be apple and if apple happens
15:26
to contain the variety that we've already specified we're going to add it to our array then we'll
15:32
go through banana does banana have our variety that we specified maybe it will
15:36
It was Honeycrisper Manzano. And it'll just go through each of our possible values here
15:42
It's going to check all of these to see is that the variety we specified
15:46
And if it is it will add this to our fruit matches array I did it like this because in the event there happens to be a variety that exists in multiple fruits
15:59
Not sure how often that happens, but just in case that happens, this is how you can account
16:03
for that. And then we're going to return our matching array. Now our else statement means we haven't specified a variety and we have specified fruit though
16:15
We just want to output all our possible values of our fruits
16:18
And if we've started typing a fruit name like AP for Apple, it will give us just tab completion to Apple
16:28
Let's take this, bring it into our current session, and let's go down and look at our updated get fruit variety function
16:36
The only major change here is on the fruit parameter. We now are specifying our fruit argument completer, just like we did previously for a variety
16:45
So we'll bring this in here. Let's clean this up. Now let's run Get Fruit Variety
16:57
Let's specify variety first. We'll type NAM for Mandarin, but it also could be Manzano, but we'll go Mandarin
17:04
And if I go to fruit now and hit tab, it's going to only give me orange, and if I keep hitting
17:09
tab, it's not giving me other fruits that it doesn't match with
17:14
So now we are searching fruits for matching varieties. And then we can also now still go to fruit, say banana, go to variety, and it's only giving me the matching varieties of banana
17:30
Argument completers, this can be a little more complicated. I made this example actually more complicated maybe than it needed to be, but I wanted two parameters that depend on each other's values
17:42
If you were only worried about one specific possible value for a parameter, you'd easily just go with values
17:48
This could also be valid, but it might be over complicating it a little bit
17:53
This was really good, I think, for having two parameters that depend on each other's values
17:59
It does get a little complicated though and took a lot of work to figure this out when I initially looked at it
18:04
Our last option here is registering argument completer. This is very similar to the examples we looked at before, but you can use this argument
18:12
completer to work on any command, not just the ones that you write
18:16
This could include commands in other modules or ones that are included in PowerShell on your system
18:22
For example, I'm going to write a custom argument completer for the ID parameter
18:26
and I want the ID to parameter to only return time zones that I'm particularly interested in
18:32
What I mean by that is if I run Set TimeZone right now and I run ID
18:39
if I start hitting tab, it just starts tabbing through my current file system
18:44
It doesn't give me possible values for a TimeZone ID. I'd have to go look that up, running Git TimeZone, and, you know, paste in the name here
18:53
What we'll do is first to find a variable script block name, TimeZone, and ID script block
18:59
We're going to pass in all the same parameters that we just did for our previous argument
19:03
completer example. And then I'm just running a filter here of possible values
19:08
I'm running Git TimeZone, listing all that are available. And then I'm looking at the display name where the value is U.S. and Canada
19:16
So let's actually just run this little part right here. That brings back, you know, Eastern, Central, Mountain, and Pacific Standard Time
19:25
I'm pulling out just the ID property and then using for each object to output that out
19:33
Let's go ahead and bring this in here. And then using the Register Argument Completer, I'm setting this for the set time zone command
19:43
The parameter name is ID and putting in my custom screen. there
19:47
So we've got all that setup. Let's clear out the screen now
19:53
If I run Set TimeZone and I run ID, if I start hitting tab, it's giving me those four
20:01
time zones that I'm only interested in that I wrote in my custom argument completer here
20:07
Now two things about this. This doesn't prevent inputting other time zones
20:13
So if this is not one of the time zones that you want to set, you can easily type in another time zone
20:18
It doesn't stop or perform any type of validation. This is just giving you possible suggestions here
20:25
The second thing is this argument completer definition only works in your current
20:29
PowerShell session. If I were to close this out and open it back up, I would lose this
20:34
I'd have to include this in some type of startup script or maybe inside my PowerShell profile
20:38
So it's available to me each and every time. That does it for this video
20:43
learned a little something about how to add tab completion to your PowerShell parameters
20:47
We have very basic things like argument completions or validate set, and a little more complicated
20:54
ones with Argument completer, especially the one I had up here for our Get Fruit variety
21:00
But if you're ever wanting to have one value of a parameter dependent on another, definitely
21:05
a way you could go using that. That does it for this video
21:09
Thank you for watching and we'll see you next time
#Computers & Electronics
#Programming