Tag Archives: Function

PowerShellGet Find-Both


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on June 11, 2019.


For years now, I’ve had a handful of personal functions living as a part of my $PROFILE script — the CurrentUserAllHosts version. This specific profile script ensures it’s available for use regardless of which PowerShell host program I use: the ConsoleHost, the ISE, VS Code, etc. One of these functions — Show-PSGalleryProject — goes out to the PowerShell Gallery and returns the current download count of all the modules and scripts I’ve added to the gallery. It also determines the combined download total.

The below image was rescued from an old Twitter post, back when the function worked as expected. Don’t mind the black background color. It was a demo of my TMOutput module. Again, this was the output produced back when the function originally worked. It’s not currently working, but with any luck that’s about to change.

Before we continue, let’s take a quick look at the underlying code that created the above output.

Set-Alias -Name Watch-PSGalleryProject -Value Show-PSGalleryProject
Function Show-PSGalleryProject {
    Param (
        [System.Array]$Projects = ('TMOutput','Start-1to100Game3.0','Get-TMVerbSynonym','SinkProfile','Show-PSDriveMenu','Switch-Prompt')
    )
 
    Foreach ($Project in $Projects) {
        $TempVar = Find-Module -Name $Project
        [PSCustomObject]@{
            Name = $TempVar.Name
            Version = $TempVar.Version
            Downloads = $TempVar.AdditionalMetadata.downloadCount
        }
        [int]$TotalDownloads += $TempVar.AdditionalMetadata.downloadCount
    }
    ">> Total downloads: $TotalDownloads"
}

At some point, and I’m not sure when the Find-Module function stopped working against scripts in the PowerShell Gallery. Additionally, Find-Script potentially stopped working against modules. Now that said, I’m not even certain when Find-Script was added and if it ever worked against modules hosted in the PowerShell Gallery. Regardless, I need a way now to rewrite this personal function, so that it invokes the correct PowerShellGet function against the proper project: Find-Module against modules and Find-Script against scripts. Before we work on that solution, here’s what my function returns now. Ugh. The Show-PSGalleryProject function’s failed invocation.

I started writing this post prior to writing the solution. Once I dug in, it really only took a few changes to see that this was working again. While it wasn’t difficult, I’ve opted to continue the post and share my results, even if, you’ve already calculated a way in which a single function could check one function (Find-Module), before another (Find-Script). Here’s my updated code and the current, and corrected, results.

Set-Alias -Name Watch-PSGalleryProject -Value Show-PSGalleryProject
Function Show-PSGalleryProject {
    Param (
        [System.Array]$Projects = ('TMOutput','Start-1to100Game3.0','Get-TMVerbSynonym','SinkProfile','Show-PSDriveMenu','Switch-Prompt')
    )
 
    Foreach ($Project in $Projects) {
        If (Find-Module -Name $Project -ErrorAction SilentlyContinue) {
            $TempVar = Find-Module -Name $Project
        } ElseIf (Find-Script -Name $Project) {
            $TempVar = Find-Script -Name $Project
        }
        [PSCustomObject]@{
            Name = $TempVar.Name
            Version = $TempVar.Version
            Downloads = $TempVar.AdditionalMetadata.downloadCount
        }
        [int]$TotalDownloads += $TempVar.AdditionalMetadata.downloadCount
    }
    ">> Total downloads: $TotalDownloads"
}

It’s not too much slower than it used to be, but it’s always been somewhat slow. There is without question, a noticeable delay doing separate and individual searches against the PowerShell Gallery (and -Filter didn’t seem to return more than a single result the last time I checked). It’s fine for me, however (for now). I have considered doing the search as a part of a background process or when the profile is loaded. Either way, I’m back in business for today.

Scoping Out the Scope

Scope is awesome; it really is. In my mind, it is a protectionary layer to ensure things like variables are somewhat protected from say, modification. It can become a necessary concept for people using Powershell to understand. As I worked through some ideas recently, I ended up writing a function — this has been known to happen. As I sat there and fully comprehended what I had written, I began to realize that it may be worth sharing, so others can see it too. Sometimes we just need a simple example and a corresponding explanation. That has kind of been my goal over the years. Anyway, let’s focus on this function and I’ll explain what it is we have here and what it is doing.

The function is named, Get-TheVariable and it contains four commands and four comments, although two of those comments are just dashed lines. I don’t usually write comments anything like this, but I really want to break up the commands into two segments, to make sure I am as clear as I can be, as we walk through this, together. Those segments are the Global scope and the Local scope, but we will get to that soon enough. Down beneath the function are two separate commands separated by a semi-colon. The first command just clears the screen — because sometimes I need that to stay clear-headed — and the second invokes the Get-TheVariable function. Have a peek at the function now and then we will discuss it further below.

function Get-TheVariable {
    # Set and then get the GLOBAL version of the "Variable1" variable.
    # ----------------------------------------------------------------
    Set-Variable -Name Variable1 -Value 'G' -Scope Global -Description 'Scope:Global'
    Get-Variable -Name Variable1 -Scope Global | Select-Object -Property Name,Value,Description

    # Set and then get the LOCAL version of the "Variable1" variable.
    # ---------------------------------------------------------------
    Set-Variable -Name Variable1 -Value 'L' -Scope Local -Description 'Scope:Local/Function'
    Get-Variable -Name Variable1 -Scope Local | Select-Object -Property Name,Value,Description
}

Clear-Host; Get-TheVariable

So what is happening here? Before we go there, know something about how Set commands usually work. They work like New commands, unless whatever we are to trying to create already exists. In that case, they just make modifications.

The first two commands create, or modify a variable (if it already exists) and then return the variable. It is named “Variable1,” its value — like what it stores — is “G,” it is globally scoped, and it includes a description indicating that. It is created by the function, but not inside the function. It is created outside of the function, in the global scope.

The second two commands kind of do the same things, They create, or modify a variable (if it already exists) and then return the variable. It is named “Variable1,” its value — what it stores — is “L,” it is locally scoped, and it includes a description indicating that. It is also created by the function, but it is inside of the function. It is created inside of the function, in the local scope. While these variables have many things in common, such as their name, they are different.

In the end — and sorry for all the repetition — there are going to be two variables with the same name. This is perfectly okay since they are going to be created in different scopes. In real life, I would recommend not using the same name for variables even if they are scoped differently. For this example, and for fully understanding this concept, it is important we do it this way.

The below output is created by our function. In the first line, we have information about our globally scoped “Variable1” variable that was created by the function, outside of the function. Because it is globally scoped it is going to persist outside of the function. In the second line, we have information about our locally scoped “Variable1” variable that was created by the function, inside the function. Because it is locally scoped, it is not going to persist outside the function.

During the invocation of the function, the function’s scope is the local scope. When the function’s invocation is done and over, the local scope is the global scope (again). The local scope is always the current scope.

Name      Value Description
----      ----- -----------
Variable1 G     Scope:Global
Variable1 L     Scope:Function

When the function has ended, the locally scoped “Variable1” variable stops existing. The global version, however, is still alive and well. Let’s prove it.

Get-Variable -Name Variable1 -Scope Global | Select-Object -Property Name,Value,Description
Name      Value Description 
----      ----- ----------- 
Variable1 G     Scope:Global

In the above example, using -Scope Global was not necessary. Why? It’s because, again, we’re in the global scope and so there’s no need to tell the command to check a scope we are already in.

But, look at this.

Get-Variable -Name Variable1 -Scope Local | Select-Object -Property Name,Value,Description
Name      Value Description 
----      ----- ----------- 
Variable1 G     Scope:Global

What, the local variable still exists!? Uh, no, not the one created by the function for inside the function. This is kind of a recap — maybe the third or fourth one now, who knows. Again, now that we are outside of the function and back in the global scope, the Local parameter value returns the globally scoped variable, too. See the Value and Description properties? They are the same as the global variable because the local variable is the global variable when we are in the global scope.

I am beginning to think I should have left this topic to someone else. Read it a few times and if maybe I have confused you — ask questions, Read more on about_Scopes. I feel good about this post, I just likely need to read it like 10 more times and ensure it mostly makes sense.

I do want to mention this real quick because it comes up often, and good for you for reading this far; it may just pay off. If you are invoking a function and you reference a variable that has not been created or defined in the function, PowerShell will look for it outside of its local scope. That’s to say that it will go looking for the variable you didn’t assign, in the global scope, also known as the parent scope. And with the introduction of that new term, the function’s scope has another name, too. It is the child scope. Okay, I’m done. Hopefully, this didn’t confuse anyone. Phew.

Pass Range to Function Parameter

I had one of those thoughts, where you must know how something is handled in PowerShell, as soon as you possibly can. Maybe it happens to you, too. Unfortunately for me, it happen just before I decided to go to bed a few days ago, which kept me awake a bit longer than expected.

The question was this: Can I pass a range operator to a PowerShell function as a parameter? As you may know, a range operator alone isn’t worth much, so essentially the question was, Can I pass two numbers and range operator to a PowerShell function as a parameter?

Let’s start with an example of the range operator (..). It’s two dots between two numbers, and it essentially says, “provide me every number in this range.” The below example uses the numbers 1 through 10. Once executed, it returns the numbers 1, 2, 3, 4, etc., all the way up to 10.

[PS7.2.0][C:\] 1..10
1
2
3
4
5
6
7
8
9
10
[PS7.2.0][C:\]

A couple of things: One, the range operator returns an array, and two, as of PowerShell version 6, it works with letters, or characters, as well. Be sure to use single quotes around your string values, or it’ll think you are passing in command names, which will not work.

[PS7.2.0][C:\] (1..10).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

[PS7.2.0][C:\] 'a'..'e'
a
b
c
d
e
[PS7.2.0][C:\] 

Let’s review this function first. This is my New-RangeOption function. It requires that two integers be supplied. One for the RangeFirstNumber parameter and one for the RangeLastNumber parameter. The idea here is that we don’t believe a range can be supplied to a function, as a parameter, and we must create it ourselves inside of our function. We do more than just that, however. As I was just testing, we display each number and then we display the type of each parameter value. They are cast as integers, so that really wasn’t necessary. Additionally, we create the range (add the operator between the two integers), display the results, and return the type of the range, as well. This should make sense when you see the below results.

function New-RangeOption {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [int]$RangeFirstNumber,

        [Parameter(Mandatory)]
        [int]$RangeLastNumber
    )
    $RangeFirstNumber;  $RangeLastNumber
    $RangeFirstNumber.GetType(); $RangeLastNumber.GetType()

    $InFunctionRange = $RangeFirstNumber..$RangeLastNumber
    $InFunctionRange
    $InFunctionRange.GetType()
}
New-RangeOption -RangeFirstNumber 1 -RangeLastNumber 10
1
10

IsPublic IsSerial Name                                     BaseType        
-------- -------- ----                                     --------        
True     True     Int32                                    System.ValueType
True     True     Int32                                    System.ValueType
1
2
3
4
5
6
7
8
9
10
True     True     Object[]                                 System.Array

This option works, but we can do better. This, below example, has only a single parameter, Range. We send in our range, which includes the two numeric values and the range operator. It outputs the range — all the numbers — the type, and the count, as in the count of the number of members in the array. It’s 10, one for each value from 1 to 10.

function New-BetterRangeOption {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [array]$Range
    )

    $Range
    $Range.GetType()
    $Range.Count
}
New-BetterRangeOption -Range (1..10)
1
2
3
4
5
6
7
8
9
10

IsPublic IsSerial Name                                     BaseType    
-------- -------- ----                                     --------    
True     True     Object[]                                 System.Array
10

There is an important thing to notice here, and that’s the group operator — the parentheses around 1..10. It is used to pass in the digits with the range operator. Without this operator, it would’ve been a mess — let’s see.

New-BetterRangeOption -Range 1..10
1..10

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
1

In this last output, it doesn’t display the actual digits within the range. While it still thinks it’s an array, it’s an array that contains 1..10 as a single, string member. That’s it though; curiosity satisfied. We can pass in a range, using the range operator (and the group operator), to our functions. Now, I only need a reason to do it.

Prep Command for Immediate Use

Edit: I’ve updated this post by adding a really quick video at the bottom — check it out. It briefly shows the results of the concepts in this post.

It was just yesterday when I wrote the Windows Terminal Fun Surprise post. This was a short post about executing the Alt+Shift+D keyboard key combination inside Windows Terminal. I got myself to a point where I could programmatically send in this key combination using the below commands.

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('%+D')

This got me thinking: Can I use this to prep a command for immediate use? I know, what does that even mean? My profile script $PROFILE, contains a good deal of helpful tools. And then it has something like the PowerShell ASCII art below. It’s actually a work logo, but you get the idea. This laptop with the PowerShell logo will stand in just fine, however.

Function Show-PowerShellAsciiArt {
@"
   ._________________.
   |.---------------.|
   ||   PowerShell  ||
   ||     \         ||
   ||      \        ||
   ||      /        ||
   ||     / ------  ||
   ||_______________||
   /.-.-.-.-.-.-.-.-.\
  /.-.-.-.-.-.-.-.-.-.\
 /.-.-.-.-.-.-.-.-.-.-.\
/______/__________\___o_\
\_______________________/

"@
} # End Function: Show-PowerShellAsciiArt.
Show-PowerShellAsciiArt

As cool as I think this logo is — the one I actually use — and as certain as I am about it appearing every time I open a new terminal, I feel like it’s in the way when I want to start to use my terminal. Like you, I suspect, I want my prompt and cursor in the top-leftish corner and ready to go pretty quickly. The difference is, I’m willing a momentary slowdown. I can do better, however.

The thought was: Can I place a command at the prompt as a part of my profile script, that will allow me to just press Enter to clear the screen? You see, I usually type cls or clear, press Enter and then start with whatever I’m doing. I want the logo, but I also want to get started as soon as I can. The answer to that most recent thought though is yes.

In addition to the above Show-PowerShellAsciiArt function, is one more brief function and a modification to the code that started off this post.

Function Clear-Logo {Clear-Host}

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('Clear-Logo')

Now, when my profile script executes, it places the “Clear-Logo” text at my prompt, allowing me to just press Enter to clear the image and start doing what I do at the command line. As you can see, Clear-Logo is only there to invoke Clear-Host. Still, I like it better.

Press Enter and voilà.

In order to get a better idea of how this works — just in case, I didn’t do a great job explaining it, who knows — I’ve included a short video below. This shows me opening two new tabs in Windows Terminal and pressing Enter after each is open, and therefore, clearing the screen (the logo).

Windows Terminal Fun Suprise

I saw a Tweet today from Kayla Cinnamon: https://twitter.com/cinnamon_msft/status/1455946220476657665. Here’s the Tweet.

Although she wrote to type Alt+Shift+D, she obviously didn’t mean it that way. I looked past that, although some of the comments proved that others did not. I quickly figured out what it did and then I determined how to not use that keyboard key combination. Before we see that, let’s determine what Alt+Shift+D does.

Alt+Shift+D opens up a new terminal, or rather, it duplicates the pane. If you take a look at the Settings menu you’ll quickly discover a large number of other fun surprises (a.k.a. keyboard key combinations). Someday, I’ll take the time and work through those.

Anyway, here’s what you shouldn’t do.

[PS7.1.5][C:\] Add-Type -AssemblyName System.Windows.Forms
[PS7.1.5][C:\] 1..10 | ForEach-Object {[System.Windows.Forms.SendKeys]::SendWait('%+D')}

This lovely piece of frustration — if you can sneak it into a “friend’s” profile script — will add the System.Windows.Forms assembly and then send in the Alt+Shift+D key combination for each iteration through the ForEach-Object loop. In this example, it would be a total of ten times. So it’s been said, the SendWait method includes “%” and “+” in addition to the letter “D.” As you may have already guessed, if you didn’t know, these two symbols are used for Alt and Shift, respectively. So what’s this produce? It does what a good number of people that replied to Kayla’s Tweet showed us they could do by repetitively pressing the key combination. That said, why do it manually? It’s PowerShell!

Back to when she said to “type” the keyboard command. It did make me think to do something with the string value Alt+Shift+D.

In the top-left pane, which was the only one when I started, I created a function called Alt+Shift+D. It included the two commands from earlier. Then I tested it/invoked my new function and it added the pane on the right side. Then I created an alias for my function, because who wants to type out the entirety of Alt+Shift+D each time — not me — and tested it too. It opened the bottom-left pane. If you aren’t using Windows Terminal then you should be; it continues to get better all the time.

AWS PowerShell Command Count

Back in the day, I was astonished by the number of PowerShell commands that AWS (Amazon Web Services) had written. It was a huge number—I believe it was around 5,000. There’s probably a post or two on this site where it’s mentioned. Based on the number, it was clear that AWS had made a commitment to (wrapping their APIs with) PowerShell. Back then, it was easy to calculate the number of commands because of that single, PowerShell module. Install the module, count the commands (programmatically of course), and done. Over the last two or three years—I’m not 100% sure—-they moved to a model where modules are separated out by service. I greatly suspect that the single-module model was no longer suitable. Who wants to import a few thousand commands by default, when they only needed maybe one or two? Over time, I suspect that it probably wasn’t the most efficient way in which to handle the high number of commands.

Now, AWS has modules with names that begin with AWS.Tools. In the below example, I have assigned the $AWSModules variable with all the AWS modules located in the PowerShell Gallery that begin with the name AWS.Tools.*.

$AWSModules = Find-Module -Name AWS.Tools.*
$AWSModules.Count
241
$AWSModules | Select-Object -First 10 -Property Name,Version
Name                              Version
----                              -------
AWS.Tools.Common                  4.1.10.0
AWS.Tools.EC2                     4.1.10.0
AWS.Tools.S3                      4.1.10.0
AWS.Tools.Installer               1.0.2.0
AWS.Tools.SimpleSystemsManagement 4.1.10.0
AWS.Tools.SecretsManager          4.1.10.0
AWS.Tools.SecurityToken           4.1.10.0
AWS.Tools.IdentityManagement      4.1.10.0
AWS.Tools.Organizations           4.1.10.0
AWS.Tools.CloudFormation          4.1.10.0

Once I determined that there were over 240 individual modules, I piped the $AWSModule variable to the Get-Member command to view its methods and properties. I didn’t know what I was after, but out of the returned properties, I was intrigued by the Includes property. I used that property as a part of the below command. Each of the 241 entries includes a Command, DscResource, Cmdlet, Workflow, RoleCapability, and Function nested property inside the Includes property.

$AWSModules.Includes | Select-Object -First 3
Name Value
---- -----
Command {Clear-AWSHistory, Set-AWSHistoryConfiguration, Initialize-AWSDefaultConfiguration, Clear-AWSDefaultConfiguration…}
DscResource {}
Cmdlet {Clear-AWSHistory, Set-AWSHistoryConfiguration, Initialize-AWSDefaultConfiguration, Clear-AWSDefaultConfiguration…}
Workflow {}
RoleCapability {}
Function {}
Command {Add-EC2CapacityReservation, Add-EC2ClassicLinkVpc, Add-EC2InternetGateway, Add-EC2NetworkInterface…}
DscResource {}
Cmdlet {Add-EC2CapacityReservation, Add-EC2ClassicLinkVpc, Add-EC2InternetGateway, Add-EC2NetworkInterface…}
Workflow {}
RoleCapability {}
Function {}
Command {Add-S3PublicAccessBlock, Copy-S3Object, Get-S3ACL, Get-S3Bucket…}
DscResource {}
Cmdlet {Add-S3PublicAccessBlock, Copy-S3Object, Get-S3ACL, Get-S3Bucket…}
Workflow {}
RoleCapability {}
Function {}

After some more inspection, I decided that each command populated the Command property and the Cmdlet or the Function property, as well, depending on what type of commands were included in the module. Next, I just returned the commands using dot notation. This isn’t all of them, but you get the idea.

$AWSModules.Includes.Command
Clear-AWSHistory
Set-AWSHistoryConfiguration
Initialize-AWSDefaultConfiguration
Clear-AWSDefaultConfiguration
Get-AWSPowerShellVersion
...
Remove-FISExperimentTemplate
Remove-FISResourceTag
Start-FISExperiment
Stop-FISExperiment
Update-FISExperimentTemplate

If you’re curious, as I was, Update-FISExperimentTemplate, “Calls the AWS Fault Injection Simulator UpdateExperimentTemplate API operation.” I have no idea.

With a little more dot notation, I was able to get the count.

$AWSModules.Includes.Command.Count
8942

And now, I know what I used to know. I also know that AWS has been busy. And, that they’ve continued to make a huge effort in the PowerShell space. If you don’t go straight to the PowerShell Gallery, and I would recommend you do, you can always start at the AWS PowerShell webpage.

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.

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

Functions Finding Variable Values

Every once in awhile I forget something I know, I know. Not sure why, but for a quick moment, I suddenly couldn’t remember which can access what. Can a nested, or child, function, access the variable in the containing, or parent, function, or is it the other way around? That is, can a containing, or parent, function access the variable in the nested, or child, function? Even as I write this, I still can’t believe that for a moment I forgot what I’ve known for so long.

Duh. A nested or child function can access a variable assigned in the containing or parent function. If the child function cannot find a declared variable inside itself, it’ll go upward in scope to its mom or dad, and ask if they have the variable assigned, and if so, they can borrow it. Being good parents, they offer it if they have it.

It’s so obnoxious that in a quick moment, I wasn’t sure, even though I’ve pictured it in my own head multiple times, as well as relied on it. Anyway, here’s an example that does prove that a nested function will go upward to find a variable, if it’s not been assigned inside itself.

Function AAA {
    'In function AAA.'
    Function BBB {
        'In function BBB.'
        $x = 5
    }
    $x
    BBB
}
AAA

Function CCC {
    'In function CCC.'
    $x = 5
    Function DDD {
        'In function DDD.'
        $x
    }
    DDD
}
CCC
In function AAA.
In function BBB.
In function CCC.
In function DDD.
5

This got me wondering, how far up will it go!? I assume it’ll go up and up and up, and well I was right. Take a look at this example. I guess if I had to forget something so simple that at least I got an opportunity to build this example. Enjoy the weekend!

Function Top {
    $x = 10
    "0. Assigned `$x with $x."
    "1. Inside the $($MyInvocation.MyCommand.Name) function."
 
    Function MidTop {
        "2. Inside the $($MyInvocation.MyCommand.Name) function."
 
        Function MidBottom {
            "3. Inside the $($MyInvocation.MyCommand.Name) function."
 
            Function Bottom {
                "4. Inside the $($MyInvocation.MyCommand.Name) function."
                If (Get-Variable -Name x -Scope Local -ErrorAction SilentlyContinue) {
                    '5. Can find the variable in the local scope.'
                } Else {
                    '5. Cannot find the variable in the local scope.'
                }
                "6. The value of `$x is $x."
            } # End Function: Bottom.
            Bottom
 
        } # End Function: MidBottom.
        MidBottom
 
    } # End Function: MidTop.
    MidTop
 
} # End Function: Top.
Top
0. Assigned $x with 10.
1. Inside the Top function.
2. Inside the MidTop function.
3. Inside the MidBottom function.
4. Inside the Bottom function.
5. Cannot find the variable in the local scope.
6. The value of $x is 10.

Update: I made a few modifications to the above function. Now, it indicates that it cannot find the $x variable in the local scope of the Bottom function. That’s one way to help prove it goes upward through the parent functions looking for a value for $x.

Add Measure-Command into a Function

During an at work demonstration of my Advanced Function Template recently, I heard an interesting idea from a teammate. It all started when I mentioned how many lines of code my function template required, without having even added any non template code to it. In version 1.0, the template required around 75 lines, which felt high. In subsequent versions it crept up past 100 — maybe 120. That also felt high, until I ran some tests and stopped caring. It’s probably past 120 by now, but I’ve stopped paying attention, as it no longer matters to me.

In my testing, a function that includes only a single string has an execution time of around 5,000 ticks. With 10,000 ticks per millisecond, you’re right to think that’s fast. With my most current version of the Advanced Function Template, with no additional code, it comes in at 3 – 4 milliseconds. Personally, I can’t tell the difference between a 1/2 of a millisecond and a few, and so the number of lines required by the template stopped meaning much to me. Especially since it only includes relevant and necessary code anyway. It’s worth it, especially since we now have region support in Visual Studio Code and can collapse any code we don’t want to have to constantly view anyway.

The interesting idea that was brought up, was to add a way to measure the execution of the Advanced Function Template (and its included, user based code [once that’s been added]), from within the function that’s been built using the template itself. I had never thought of such a thing. This is to say, wrap the Measure-Command cmdlet inside the template, in order to be able to run it against itself. If that’s confusing, no worries, the following code example should help explain.

Function Get-TheAlias {
    [CmdletBinding()]
    Param (
        [string]$Definition,
        [switch]$Measure
    )
    
    If ($Measure) {
        [System.Void]($PSBoundParameters.Remove('Measure'))
        Measure-Command -Expression {Get-TheAlias @PSBoundParameters}
    } Else {
        Get-Alias @PSBoundParameters
    }
}

The first two below examples do not use the Measure switch parameter offered in the above function. Therefore, they’ll head down the Else path of the If-Else statement. The first example will internally run Get-Alias with no parameters or parameter values (not all results are displayed). The second example also uses Get-Alias internally, however, it does so using the Definition parameter and a parameter value. The Definition parameter allows us to determine if there’s an alias, or aliases, for the given cmdlet or function. As there is, an alias for Get-Command is displayed.

PS > Get-TheAlias

CommandType     Name                                Version    Source
-----------     ----                                -------    ------
Alias           % -> ForEach-Object
Alias           ? -> Where-Object
Alias           ?: -> Invoke-Ternary                3.2.0.0    Pscx
Alias           ?? -> Invoke-NullCoalescing         3.2.0.0    Pscx
Alias           ac -> Add-Content
Alias           And -> GherkinStep                  4.1.0      Pester
...
Alias           gwmi -> Get-WmiObject
Alias           h -> Get-History
Alias           hib -> Suspend-Computer
Alias           hibernate -> Suspend-Computer
Alias           history -> Get-History
Alias           i -> powershell_ise.exe

PS > Get-TheAlias -Definition Get-Command

CommandType     Name                                Version    Source
-----------     ----                                -------    ------
Alias           gcm -> Get-Command

The next two examples of the Get-TheAlias function do use the Measure parameter. In the first example, we only use the Measure parameter. Because the code dictates, we’ll head down the If portion of our If-Else language construct. It will strip the Measure parameter, and then measure the time it take to run a second, internal instance* of the Get-TheAlias function (which again, simply runs Get-Alias). When we also include the Definition parameter and a value, we’ll again strip the Measure parameter, and internally run a measured copy of Get-TheAlias function. This will again, execute Get-Alias with the Definition parameter and the submitted parameter value.

* Update: I do want to mention here, that there’s really no internal instance of the function. When a function calls itself, it looks for the function in it’s own scope (the child scope, inside the function). When it cannot be located there, it goes upward, to the parent scope, where it will find the declared function. Maybe this helps.

PS > Get-TheAlias -Measure

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 6681
TotalDays         : 1.75694444444444E-08
TotalHours        : 4.21666666666667E-07
TotalMinutes      : 2.53E-05
TotalSeconds      : 0.001518
TotalMilliseconds : 1.518

PS > Get-TheAlias -Definition Get-Command -Measure

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 1
Ticks             : 10287
TotalDays         : 7.28819444444444E-09
TotalHours        : 1.74916666666667E-07
TotalMinutes      : 1.0495E-05
TotalSeconds      : 0.0006297
TotalMilliseconds : 0.6297

It’s weird and different sure, but most of all, I need some thoughts on whether or not this will actually work long term. In my mind it does. It tries to run a function from its own function scope, when it can find that function, it goes up to the global scope and runs itself without the measure switch parameter. Sorry, if you’re still confused, but I’m finding it difficult to be 100% pleased with my ability to describe things.

If you’re not confused by this, then walk thought the function a time or two in your own head, or maybe even on a computer! Can you create or think of scenarios where adding a way to measure how long a function takes to complete can’t, or shouldn’t, be added to itself?