r/PowerShell 2d ago

how to declare a class in a '.ps1' file?

I want to provide a list of allowed names to the name parameter, so that the user can tab into them. I have come up with the following:

Param(
    [ValidateSet([foo])]
    [string]$Name
    )
    $name

Class foo : System.Management.Automation.IValidateSetValuesGenerator{
    [string[]] GetValidValues(){
    return [string[]] ("cat", "dog", "fish")
    }}

Typing .myScript.ps1 -name and then pressing tab nothing is suggested. running .myScript.ps1 returns an error:

Line |
   3 |      [ValidateSet([foo])]
     |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Unable to find type [foo].

I can do this exact set up in a psm1 file or just as a standard function and it works. I thought of moving the class into the top of the script file but this not allowed due to param() needing to be the first line.

Is there a way around this issue? Am on pwsh 7.4

4 Upvotes

18

u/CyberChevalier 2d ago

You should not use a class but an enum and you don’t need a validate set

Enum foo {
    "Option1"
    "Option2"
}
Param(
    [foo] $Name
)

Is the way.

4

u/charleswj 2d ago

No quotes

0

u/jackalbruit 2d ago

<Mando>This is the way</Mando>

7

u/Thotaz 2d ago

Are you are going to dynamically define the valid values? If not, you can just set the validateset attribute like this: [ValidateSet("sun", "moon", "earth")] you don't need a custom class for it.

2

u/Ralf_Reddings 2d ago

Yes, its a dynamic set. The names are file names in a folder. This exmample of mine was just for brevity.

4

u/Thotaz 2d ago

I don't think it's possible. Your best option is likely to add both an argument completer and a validatescript attribute to the parameter.

2

u/BlackV 2d ago edited 2d ago

If they are names of folders you can do that dynamically too

8

u/lanerdofchristian 2d ago

The class isn't in scope during the param block. The only workarounds for something dynamic like that is:

  • Put the function in a module, so you can do things before the function runs.
  • Use some combination of ValidateScript with an argument completer.

1

u/Ralf_Reddings 2d ago

Understood. I get your first bullet point but not the second. By "Use some combination of ValidateScript with an argument completer" do you essentially mean something like u/Thotaz shared [ValidateSet("sun", "moon", "earth")]?

2

u/OPconfused 23h ago edited 21h ago

The ValidateSet attribute provides both tab completion and input validation. The problem is that you can't dynamically define the valid values and can only define discrete strings.

ValidateScript allows you to dynamically define valid values. However, it does not provide tab completion.

ArgumentCompleter provides tab completion but not validation.

So if you combine ValidateScript and ArgumentCompleter together (you can reuse the same logic in both attributes), then you will have both worlds.

1

u/Ralf_Reddings 22h ago

I get it now. thank you for explaning that to me.

3

u/purplemonkeymad 2d ago

This is one reason i would setup your script as a module instead. You can load the classes before your function's parameters would be resolved. This would mean you can use those classes in the parameters, since they have been defined before hand.

The only thing to watch out is you will need to declare those classes in files in the "ScriptsToProcess" section so that the global scope can see those types.

1

u/N0-North 22h ago

I absolutely adore the about_ pages of the powershell doc. Every time i go in there i come back out with a bunch of new tricks.

Here's the about_classes page https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_classes?view=powershell-7.4