Category Archives: Quick Learn

Practical examples of PowerShell concepts gathered from specific projects, forum replies, or general PowerShell use.

Self-Destruction Script

As a part of some work I’m doing, I’ve decided that I need a specific script to do some final configuration for me. More to come on that in time, perhaps. The point here, however, is that a PowerShell script completes my final configuration, and when this final configuration is complete, I have no need for my script any longer. Can a PowerShell script delete itself? Sure it can.

How does a script delete itself? Easy. It’s a single line, really. However, I have a mildly more interesting example. Before you take a look at the included gif — this really does lend itself to a visual — here are the contents of my self-destruction script. It echos an indication that the script will self-destruct. At this point, it begins a countdown in seconds, from 5 to 1, before removing itself — the script file — from the file system. Take a look, and maybe, just maybe, you’ll find yourself in need of something like this sometime, too.

Write-Output -InputObject 'This script will self-destruct in 5 seconds.'

5..1 | ForEach-Object {
    If ($_ -gt 1) {
        "$_ seconds"
    } Else {
        "$_ second"
    } # End If.
    Start-Sleep -Seconds 1
} # End ForEach-Object.

Write-Output -InputObject 'Self destruction.'

# Here's the command to delete itself.
Remove-Item -Path $MyInvocation.MyCommand.Source

And here’s, the visual representation. It’s a bit small, but you should be able to determine what’s happening. The script executes on the left, and once the countdown is over, DeleteSelfScript.ps1 is removed on the right. It’s deleted itself.

Enjoy the week, and learn something new!

Read-Host Prompt inside Hash Table

I’m working with Plaster and in doing so, have a few hash tables in my workspace. Suddenly, and out of nowhere, I had a thought: Can I put a Read-Host command, as the value of a key-value pair inside of a hash table? Yes, but before we get too far into this, here’s a simple hash table example without any Read-Host commands.

$Params = @{
    meal = 'lunch'
    food = 'Ramen'
    drink = 'Tea'
}
$Params

Name                           Value
----                           -----
drink                          Tea
food                           Ramen
meal                           lunch

Okay, that was simple: Choose a variable name, prefix it with a dollar sign, create the hash table structure as @{}, and then add key-value pairs as <key> = <value>.

Now, on to a Read-Host example. Read-Host is a cmdlet that can be used to interactively prompt a user for a value. It’s not always preferred, or the right to do, but there are times when it can be useful, and where its use is acceptable. In the next example, we’ll create a single key-value pair ourselves, then we’ll prompt the user for a value to assign to the Number key. As you will also determine, when prompted, the word “five” is entered, and sure enough, the entry ends up as the value for the Number key in our hash table.

Remove-Variable -Name Params
$Params = @{
    Color = 'red'
    Number = Read-Host -Prompt 'Enter spelled out number'
}
$Params
Enter spelled out number: five

Name                           Value
----                           -----
Number                         five
Color                          red

This example was one of those “can I do this moments?” Sure, I was able to return the $Params hash table with the correct value in the Number key after having been prompted for that value. This occurred, as the hash table was being defined and built. I didn’t even know if that would work, but now we both know that it does.

In the next example, we try something even more obscure, but as suspected, it doesn’t work. We first assign the Random key the value of a Read-Host prompt. As we’ve seen, that does work. Next, while still creating keys and values, we take the value assigned to the Random key in the $Params hash table and attempt to assign it to the Number key, as well. Again, this doesn’t work. As suspected, we can’t use the $Params hash table until it’s done being created. It’s not a shock, but it was worth a try, of course.

Remove-Variable -Name Params
$Params = @{
    DayOfWeek = 'Friday'
    Random = Read-Host -Prompt 'Enter spelled out number'
    Number = $Params.Random
}
$Params
Enter spelled out number: three

Name                           Value                                                                                                                             
----                           -----                                                                                                                             
Random                         three
Number
DayOfWeek                      Friday

Even if this did work, I’m not sure how this would ever really serve a purpose, but it’s always worth trying something even if doesn’t seem plausible. And if it did work, I don’t expect that we’d even need duplicate values in different keys anyway, and that’s all we’d be doing. But all that said, we absolutely can use Read-Host to assign a value to a key in a hash table, as it’s being created.

Alright, that was it. Enjoy the week!

Array (Not Hash Table) Splatting


Welcome to the 298th post on tommymaynard.com. It’s countdown to 300!


I’ve been interested… okay fine, consumed with PowerShell for five, maybe six years. At some point along the way, I learned how to splat. That’s where you create a hash table of keys and values (think parameters and parameter values), and include those when invoking a cmdlet or function. Here’s an example of not splatting, and splatting, a hash table.

Get-Process -Name firefox -FileVersionInfo -OutVariable FirefoxProcess

The above example shows how we might typically write this command. The below example uses splatting. To be more specific, it uses hash table splatting.

$Params = @{
    Name = 'firefox'
    FileVersionInfo = $true
    OutVariable = 'FirefoxProcess'
}
Get-Process @Params

Well today, many years since I started this journey, I find myself suddenly exposed to array splatting. Huh? You might be able to guess how this works. We’re still going to issue a command, and we’re still going to use the at sign (@) in front of our variable name, but this time, our variable is going to contain an array, not a hash table. Let’s take a look at a few more examples.

Get-ChildItem -Path '.\Documents\test\' -Filter '*.rtf'
Get-ChildItem '.\Documents\test\' '*.rtf'

In the first above Get-ChildItem example, you can see how we might typically issue this command. In the example below that one, we’re running the same above command; however, we’re doing so without including the Path and Filter parameter names. Because this still works, we know these two parameters are positional (or because we read the help first). This means they can be used correctly without the need to include their matching parameter names. Because they’re positional, we can use array splatting. Take a look at the final below example.

$Params = '.\Documents\test\','*.rtf'
Get-ChildItem @Params

Just a quick reminder. We should always use parameter names in code we expect to been seen, or used, more than once, such as code inside functions and scripts. It’s good to know about this, but it might be something to avoid. Using hash table splatting may still be the way to go, as it keeps our parameter names around.

Wrapper Function with Pause


Welcome to the 296th post on tommymaynard.com. It’s countdown to 300!


I’ve recently written a wrapper function. Its purpose is to allow a user the ability to invoke four different functions, by only invoking one — the wrapper. As of part of this assignment, I wrote out a quick function to do some testing. I wanted to determine whether or not I’ll allow the wrapper function the ability to pause between the invocation of each wrapped function. At this point, I think that will be included.

So it’s written down somewhere, here’s what I quickly jotted to test this scenario.

Function Test-Pause {
    [CmdletBinding()]
    Param (
        [switch]$Pause
    )

    'First String'
    If ($Pause) {Pause}
    'Second String'
    If ($Pause) {Pause}
    'Third String'
    If ($Pause) {Pause}
    'Fourth String'
}

If the Pause parameter isn’t used, the function never stops. In my test, it just works top to bottom, echoing out each string stored inside the Test-Pause function. That works as expected!

PS > Test-Pause
First String
Second String
Third String
Fourth String

If the Pause parameter is included, however, it will pause after each time a string is written. Once the user presses Enter, it continues until it’s required to pause again, providing it is.

PS > Test-Pause -Pause
First String
Press Enter to continue...:
Second String
Press Enter to continue...:
Third String
Press Enter to continue...:
Fourth String

That was it. I just wanted a place to store this, just in case I find myself in a situation where it might be useful again outside my newest function. Just remember, if you do something like this in an automated fashion, don’t include the Pause parameter: It’s going to require a human that way.

An Array of Hash Tables


Welcome to the 293rd post on tommymaynard.com. It’s countdown to 300!


If you’ve ever looked into Pester and TestCases, then maybe you’ve seen what I’m about to mention. While today’s post has nothing to do with Pester, this feature in Pester uses an array of hash tables. Neat, right? While adding a couple New-PSDrive commands to my PowerShell profile, I moved from this first option,…

$Param = @{
    Name = 'IDT'
    Root = '\\tommymaynard.com\Windows\Internal\Users\tommymaynard'
}
New-PSDrive @Param -PSProvider FileSystem | Out-Null

$Param = @{
    Name = 'IDI'
    Root = '\\tommymaynard.com\Windows\Internal'
}
New-PSDrive @Param -PSProvider FileSystem | Out-Null

…to something else.

While the use of splatting is a bit more grown-up than creating long, multi-line wrapping commands, there’s no reason why we should be executing the same command — New-PSDrive, in this instance — outside of a looping construct. That was my thought today, anyway. If we need to invoke the same cmdlet or function more than once, it should only be entered into our code, once. It is possible there’s an exception here, but for the most, I stick to this belief.

But how do I combine multiple hash tables in to looping construct? Well, my solution had everything to do with my quick remembrance of Pester and TestCases. For fun, we’ll link to a quick example from a Pester issue I recently created in GitHub. This offers us a good example. See the third It block in the Pester example in number 4 (“Current Behavior”). Do you see that? It’s an array of hash tables. Perfect example for what I needed.

As we create our hash tables, we need to ensure they’re being added to an array. Then we can loop through our array elements with ForEach, or ForEach-Object. I’ll include examples for both. First, however, let’s create our array of hash tables.

$HashTableArray = @(
    @{
        Name = 'IDT'
        Root = '\\tommymaynard.com\Windows\Internal\Users\tommymaynard'
    },
    @{
        Name = 'IDI'
        Root = '\\tommymaynard.com\Windows\Internal'
    }
)

And now, let’s ensure what we have, is where we want it, and want we want.

PS > $HashTableArray

Name                           Value
----                           -----
Root                           \\tommymaynard.com\Windows\Internal\Users\tommymaynard
Name                           IDT
Root                           \\tommymaynard.com\Windows\Internal
Name                           IDI

As mentioned, there’s a couple way to loop though these. One is using a Foreach construct — which is the first below example — and one is using the ForEach-Object cmdlet. They’re both below, and listed in the respective order in which they were discussed. So yes, if you’re calling the same command outside of looping construct, you need to consider how to do better, if you can. There’s no reason in my instance that New-PSDrive should be in a our code more than once.

Foreach ($HashTable in $HashTableArray) {
    New-PSDrive @HashTable -PSProvider FileSystem | Out-Null
}

$HashTableArray | ForEach-Object {
    New-PSDrive -Name $_.Name -PSProvider FileSystem -Root $_.Root | Out-Null
}

If you didn’t notice, I’ve piped my New-PSDrive commands to Out-Null. This was done in order to remove the output that would’ve otherwise been produced.

Add a Single Space Between Characters


Welcome to the 292nd post on tommymaynard.com. It’s countdown to 300!


Even though we’ve been doing this PowerShell thing awhile, we don’t always write our own solutions. You, like me, just might go looking for someone else’s solution, before spending a few minutes of your own time, writing a solution. I’m guilty. That’s said, there’s nothing wrong with it. Seeing what other people have done is often beneficial to our own solutions. It’s part of why I’ve been blogging since mid 2014; take from me, what helps for you.

There’s a very small function I’m currently writing that needed a feature. That feature, which I couldn’t locate well enough anywhere else, is something I wrote myself. Now that I have my solution, after a small amount of time yesterday, I thought I share it for someone else some day.

I needed to accept a string with or without spaces in it, and ensure each character in the string had a space between it when I was done. This ‘ a bcd ef 12 34 gh i’ needed to be come this ‘a b c d e f 1 2 3 4 g h i’. I opted first, to remove all the spaces, whether there were any or not, and then add a space between each character in the string. In case it’s helpful, I’ve include the few lines of PowerShell I wrote out to get this to suitably work for me. Have a look, and take it if you want it.

Remove-Variable -Name String,NewString -ErrorAction SilentlyContinue; Clear-Host

$String =  ' a          ll the d                ogs are so l o u d 1     2 3'
$String
Pause

$String = $String.Replace(' ','')
$String
Pause

$String.ToCharArray() | ForEach-Object {
    $NewString += "$_ "
}
$NewString

Now, let’s run it.

 a          ll the d                ogs are so l o u d 1     2 3
Press Enter to continue...:
allthedogsaresoloud123
Press Enter to continue...:
a l l t h e d o g s a r e s o l o u d 1 2 3

Perhaps you can tell what I might’ve been dealing with when I was writing this. I was at home. My wife was hosting a Girl Scout get together, and the dogs were being just a touch annoying.

The ScriptsToProcess and RequiredModules Order

Recently, I wrote this:

Even more recently, I wrote a usable fix.

Before we get there, however, let’s make sure my Tweet makes sense. First off, if you don’t know already, you need to be aware that there’s an optional file called the module manifest file that we can include alongside a script module file (a .psm1). It’s a .psd1 file and its purpose in life is to help define additional information — metadata — about a script module.

In addition to telling us about the module (the author, description, version, etc.), it can do other things for us. This includes requiring a specific PowerShell host program, requiring a specific version of PowerShell, requiring specific modules are imported when our module is imported, and also running PowerShell scripts, before our module is imported. You don’t have to use a module manifest file when you create and a use a module, but there’s so much to gain from doing so (and it’s super easy [see New-ModuleManifest]).

My problem here is that the RequiredModules section — an entry in our module manifest file — is checked before any of the scripts are run that assist to set up the environment before the module is done loading. This means that I was unable to install a PowerShell module via these scripts (called ScriptsToProcess) before RequiredModules inspected the system for the modules in which our module is dependent. Too bad. To me, and in this instance at minimum, these two module manifest entries run in the wrong order. Had ScriptsToProcess run first, I would have been able to install a PowerShell module before the required modules’ dependencies were evaluated.

To get this to work as desired, required a workaround. I thought I’d take a minute and share what I’ve done. One, we have a script module — a .psm1 file — and a module manifest — a .psd1 file. We also, have a second .psd1 file. This is key.

The first .psd1 file does not require any modules; it does not have a dependency on the system already having specific modules in place. Here’s that entry in our first, or initial, module manifest file. Do notice that RequiredModules is commented out, and therefore not read, when this file is parsed.

# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()

The next section of interest in our first .psd1 file, is ScriptsToProcess. These are standalone scripts that execute prior to our module importing. Do notice that ScriptsToProcess can accept multiple scripts. This means I can run multiple scripts, one right after another, in order that I don’t have all my code in one big script file. If you’re writing functions and not scripts, you get this. Smaller pieces of code are easier on you.

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = '.\ScriptsToProcess\InstallADDelegationModule.ps1','.\ScriptsToProcess\ReplacePsd1File.ps1','.\ScriptsToProcess\ReimportModule.ps1'

Again, we have two module manifest files for our one, script module. The first script in the above list installs the ADDelegation PowerShell module onto our system. Remember, if our first manifest file required this module, we wouldn’t be able to get it installed to our system with ScriptsToProcess. With an initial, only used once, .psd1 file, we can. The second script, copies our second manifest file over the top of the one that currently executing during this first module import. Not to worry, the manifest is only read when the module is initially imported. The updates are not going to matter just yet. That said, finally, the last ScriptsToProcess script simply imports our module again, using the Force parameter. This parameter imports a module even if it’s already been imported. In doing this second import on our module, the second module manifest becomes active.

Before we consider that our module has been imported again with an updated manifest, we need to discuss the last section in our first manifest. It’s the FunctionsToExport section. Notice that during the first import of our module, there aren’t any functions being exported. Exported functions are how functions are added to our PowerShell session for use. This hardly matters, however, since the last ScriptsToProcess script, discussed above, imports our module again. Since we forcibly import our function again with the second manifest file in place, it doesn’t matters what we do or don’t import in the first run; it becomes of no importance almost immediately. Even so, I’m keeping this hold over, because there’s no reason to do any extra work on the first import, such as importing functions that would never be used.

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @()

Hopefully I’ve explained things well enough that you’ve been able to follow along so far. Our second module manifest file, that we copied over the first manifest in the second ScriptsToProcess file, has some changes as you can see below. In this manifest file, we do require a couple modules. Remember, we installed the ADDelegation module when we were using the first manifest file. We also have a dependency on the ActiveDirectory module, but I’m expecting that is already in place by my users (for now).

# Modules that must be imported into the global environment prior to importing this module
RequiredModules = 'ActiveDirectory','ADDelegation'

Next, we have only a single ScriptsToProcess script. While a ScriptsToProcess script isn’t always a necessity, or a requirement, all this script does is verify I have all the CSV files I need for the functions in our module.

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = '.\ScriptsToProcess\TestForCsvFiles.ps1'

And lastly, we include all the functions we need exported into the PowerShell session for those using our module.

# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'New-DomainDelegationPrep','New-DeptADGroupAndRole','New-OnPremExchRole','New-O365ExchRole'

Hope you enjoyed the weekend, but it’s time to start the week again. You may have never seen it before, but we do have a way, albeit a workaround, to install modules and make them required. It takes a little extra work, but it’s doable. In my case, it’s worth the extra work.

Write-Verbose and -Warning Template

I’m pretty serious when it comes to ensuring the use of Write-Verbose and Write-Warning to their fullest potential in my functions. With that in mind, I’ve come up with a standard way in which I write these types of messages to the screen in warning conditions. In order that I don’t have to track down this code from an actual, already written function the next time I need it, I’ve opted to drop an example, right here. Perhaps it’ll be useful for you, as well.

The idea is that I assign the message I want to convey to a variable, and then use it in the Write-Verbose and Write-Warning commands. That whole use-a-variable concept isn’t what I’m here to discuss; I kind of expect that you put your strings into variables when you plan to use them more than once. The idea is that you can see the full structure I use to inform my users in these types of situations. Have a look at the below example.

...
If (Test-Path -Path $CsvPath) {
    ...
} Else {
    $Warning = "Unable to locate the ""$CsvPath"" path."
    Write-Verbose -Message "$BL Preparing and displaying a warning message."
    Write-Warning -Message $Warning
    Write-Verbose -Message "$BL WARNING: $Warning"
    Write-Verbose -Message "$BL Breaking out of the current function [$CmdType`: $CmdName]."
    break
} # End If.
...

Don’t mind the $BL, $CmdType, and $CmdName variables, as these have everything to do with the advanced function template I’ve written and use for just about everything. They’re not required for this basic code structure.

This simple Warning/Verbose design ensures that Write-Warning messages are captured by Write-Verbose. Once a warning message hits the PowerShell host program, it’s mostly lost. By using the same variable with Write-Verbose, I’m able to do other things with it. As my advanced function template can send Write-Verbose messages to log files, I can therefore ensure warning messages are logged by my functions. This is key.

Anyway, it’s nothing groundbreaking, but I am done looking for an example of this in something I’ve already written, in order to use it in something I’m writing. The two following images are the same example, both in the ISE (Integrated Scripting Environment) and in Visual Studio Code. You can see that when the Verbose parameter is used, we get the warning information included in a verbose message. Therefore, with the use of my advanced function template, I can ensure my warning message makes its way into a log file, if that’s what’s wanted.

And yes, it should’ve included “to,” as in “Unable to locate the…” Ugh, I’m not going to bother recreating these screen captures, even though I’ve updated the above code example.

Of course, all this got me thinking. In some recent projects, I’ve been doing something a bit differently. I’ve been piping my string straight to ForEach-Object, where both Write-Warning and Write-Verbose are executed. Therefore, I suppose the template may end up like it looks below. I’m going to have to think about potentially making this code example my Write-Verbose and Write-Warning template.

...
If (Test-Path -Path $CsvPath) {
    ...
} Else {
    Write-Verbose -Message "$BL Preparing and displaying a warning message."
    "Unable to locate the ""$CsvPath"" path." | ForEach-Object {
        Write-Warning -Message $_; Write-Verbose -Message "$BL WARNING: $_"
    }
    Write-Verbose -Message "$BL Breaking out of the current function [$CmdType`: $CmdName]."
    break
} # End If.
...

Either way, I’ve got this template written down somewhere now, outside of a function.

Create Function from Variable Value I

If you were here Wednesday, then perhaps you already read Get the Total Parameter Count. If not, I’ll quickly tell you about it. I wanted to know how many parameters a function had whether or not those parameters were used when the function was invoked. I did this by using Get-Help against the function, from within the function. Yeah, I thought it was cleaver too, but the best part, it gave me the results I was after. If a function had three parameters, it was indicated in the function’s output. Same goes for when it had two parameters, but of course in that instance, it indicated two.

In going along with that post I had an idea. I wanted to create a way to dynamically create a function with a random number of parameters between one and ten. Then, I could prove my code was working. Sure, I could’ve used Pester, but I was after making this work — this whole, create a function with a random number of parameters thing. I needed to figure out how to get the code for a function, stored inside a variable, into an actual PowerShell function. That might’ve been confusing, but the example code in this post will probably prove helpful.

I’ll show you what I started experimenting with for discovery purposes, and then we’ll jump into the code that actually creates my function with a random number of parameters in a soon to be released second part to this post.

First, we’ll start with a here-string variable assignment. Using a here-string allows us to include multiple line breaks within our variables. As you should be able to see, the value of $FunctionCode below is the code that makes up a simple function. It includes the function keyword, a function name, an open curly brace, the function’s actual code — there’s three lines of it — and a closing curly brace, as well.

$FunctionCode = @'
Function Show-Info {
    '*****'
    'This is function Show-Info.'
    '-----'
}
'@

As expected, when I echo my variable’s value, I get back exactly what I put into it.

PS > $FunctionCode
Function Show-Info {
    '*****'
    'This is function Show-Info.'
    '-----'
}

Now, for the fun part. I’ll include all my code and then we can discuss it line by line below.

$FunctionCode = $FunctionCode -split '\n'
$FunctionCodeCount = $FunctionCode.Count - 2
$FunctionCode = $FunctionCode[1..$FunctionCodeCount]
$FunctionCode = $FunctionCode | Out-String

The first of the above four lines assigns the variable $FunctionCode the value stored in $FunctionCode after we split it on each new line. The second of the four lines creates a $FunctionCodeCount variable, assigning it the number of lines in $FunctionCode after having subtracted 2 from its value. See if you can figure why it’s two…

The third line reassigns $FunctionCode again. In this line we only return the contents of the function. This means it doesn’t return the first line of the function, which includes the function keyword, the function name, and the open curly brace. It also will not include the closing curly brace at the bottom. Our final line reassigns the $FunctionCode for a third time, taking its current value and piping that to Out-String. This will help us ensure we add back in our line breaks.

Before we create our Show-Info function, let’s take a look at the $FunctionCode variable value now.

PS > $FunctionCode
    '*****'
    'This is function Show-Info.'
    '-----'

Using Set-Item, we’ll create our function called Show-Info regardless of whether or not it already exists. That’s the difference between New-Item and Set-Item (and most New vs. Set commands). New-Item will only work if Show-Info doesn’t already exist, while Set-Item will work even if the function already exists. It if doesn’t, it’ll act like New-Item and create the function.

Set-Item -Path Function:\Show-Info -Value $FunctionCode

And finally, entering Show-Info invokes the newly created function.

PS > Show-Info
*****
This is function Show-Info.
-----

Okay, with all that out of the way, we’re going to hit the pause button. Be sure you get what’s happening here, because we’ll pick up on this post in a very soon to be released follow-up that includes the rest of what we’re after: creating a function with a random number of parameters and testing we can calculate that number correctly. If you see an area in which I can improve this, please let me know!

Before we sign off though, let me include all the above code in a single, below code block. That may end up being helpful to one of us.

Remove-Variable -Name FunctionCode,FunctionCodeCount -ErrorAction SilentlyContinue

$FunctionCode = @'
Function Show-Info {
    '*****'
    'This is function Show-Info.'
    '-----'
}
'@

$FunctionCode = $FunctionCode -split '\n'
$FunctionCodeCount = $FunctionCode.Count - 2
$FunctionCode = $FunctionCode[1..$FunctionCodeCount]
$FunctionCode = $FunctionCode | Out-String

Set-Item -Path Function:\Show-Info -Value $FunctionCode

Show-Info

Get the Total Parameter Count

Update: There was an update to this post on May 29, 2018. See below.

It’s been a littler longer than normal for me to have not written. My entire week last week didn’t include a post; it’s so weird. Well, I’m taking a quick minute — seriously — to discuss something I wanted to do recently.

In a new project I’m on, I wanted to know how many parameters a function has from inside the function itself. I didn’t want to know how many parameter were used when the function was invoked. Had I, I would’ve used $PSBoundParameters. Again, I wanted to know how many parameter(s) the function had, whether they were used or not.

I’ll tell you what I opted to do. On that note, do however, let me know if you come up with a better idea. I didn’t give this a ton of time. That said, it doesn’t even have to be better; I’d still like to hear about it. For me, I opted to execute a Get-Help command against the function, from inside the function. I’m making this a quick post, so let’s jump to some code.

Function Check-ParameterCount {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [string]$Param01,

        [Parameter()]
        [string]$Param02,

        [Parameter()]
        [ValidateSet('Source','Destination')]
        [string]$Param03
    )

    $ParameterCount = (Get-Help -Name Check-ParameterCount).Parameters.Parameter.Count
    "There are $ParameterCount possible parameter(s) in this function not including the common parameters."
}
PS > Check-ParameterCount
There are 3 possible parameter(s) in this function.

The next example is the same example as above, however, we’ve removed the third parameter. Sure enough, the value is now two. As you may have noticed, Get-Help gets its parameter count from the actual parameter(s) themselves. Neither function has any comment-based help. Therefore, we can determine that it doesn’t use any static help we might include in regard to the parameter(s).

Function Check-ParameterCount {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [string]$Param01,

        [Parameter()]
        [string]$Param02
    )

    $ParameterCount = (Get-Help -Name Check-ParameterCount).Parameters.Parameter.Count
    "There are $ParameterCount possible parameter(s) in this function not including the common parameters."
}
PS > Check-ParameterCount
There are 2 possible parameter(s) in this function.

That was it. Again, let me know if there’s a better way.

Update: I had a thought. Because my function uses the CmdletBinding attribute, it’s possible that my function can have more than the number of parameters returned by Get-Help. Therefore, I’ve only slightly modified the above strings my function returns. It was “There are $ParameterCount possible parameter(s) in this function.”, and now it’s “There are $ParameterCount possible parameter(s) in this function not including the common parameters.” Now, it indicates it returns X number of parameters, not to include the command parameters.

PS > Check-ParameterCount
There are 2 possible parameter(s) in this function not including the common parameters.