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?