Tag Archives: advanced function template

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?

My Write-Verbose Best Practice

If you’ve read this post, and perhaps the ones before it, then you know that my advanced function templates have always used Write-Verbose statements for logging purposes. Anytime you want to log something using my template, it’s using Write-Verbose whether you know it or not. There’s multiple places you can log, and it’s all based on the value of the Log parameter (in 2.0 and greater). As of the 2.x versions, you can use … -Log ToScreen, … -Log ToScreenAndFile, and … -Log ToFile. Again, regardless of which way you choose to log, it’s Write-Verbose that’s doing the work. Okay fine, it’s really Out-File if you’re writing to a file, although my nested function is still called Write-Verbose. That said, there actually was a time when it was all Write-Verbose, even when writing to a file.

So, I’ve always had a specific way to write my logging statements. There’s no agreed upon best practice for Write-Verbose statements, but for me there is, and that’s why I’m writing again today. Maybe it’ll make sense to use this model for yourself, as well.

In the instance where something is about to happen (think, happens right after the current logging statement ends), I strive to use an “ing” word. From what I remember about my grade school years, I remember this as a present progressive. Here’s a few examples.

Write-Verbose -Message "$BlockLocation Setting required variables."
Write-Verbose -Message "$BlockLocation Checking if the current time zone is $DesiredTimeZone."
Write-Verbose -Message "$BlockLocation Collecting Forest FSMO Roles."
Write-Verbose -Message "$BlockLocation Displaying all FSMO Roles."
Write-Verbose -Message "$BlockLocation Downloading $Application installer from S3."
Write-Verbose -Message "$BlockLocation Installing $Application silently."
Write-Verbose -Message "$BlockLocation Invoking user is ""$env:USERDOMAIN\$env:USERNAME"" on the ""$env:COMPUTERNAME"" computer."

In my experience I tent to use an “ing” words about 80 – 90% of the time. There is, however, one other verb form I find myself using, and that’s the past tense verb form. Sometimes, it makes sense to indicate that something has completed even when a new logging, “ing” statement would indicated the previous logging topic had ended. Here’s a few examples from some of my functions.

Write-Verbose -Message "$BlockLocation Determined the time zone is not correctly set for $DesiredTimeZone."
Write-Verbose -Message "$BlockLocation Unhandled exception ($($Error[0].Exception.GetType().FullName))."
Write-Verbose -Message "Connected to Exchange Server: $ExchServ."

While looking for other worthy examples, I did find a few Write-Verbose statements that didn’t include either an “ing,” or “ed” verb. These are very rare for me, but they have been included at times to indicate information to whomever is reading the logging statements.

Notice, however, that they come just before a statement that uses “ing.” They probably shouldn’t be there, and wouldn’t be there if I was writing that tool today. Then again, they might be, as now that I think about it, and look over this older code, the logging differed because of its audience. In this case, having indicated the key and value already existed, did not exist, or did exist and needed correction, was important enough to convey. So much so, the “ing” word didn’t feel as though it was enough.

#region Create Registy Key(s) and add value (if necessary).
If (-Not($Exists)) {
	Write-Verbose -Message "$BlockLocation Registry key and value do not yet exist."
	Write-Verbose -Message "$BlockLocation Creating new Registry key and value."
	[System.Void](New-Item $RegKey -Force | New-ItemProperty -Name $RegName -Value $RegValue -Force)
	$CheckChange = $true
	$FixType = 'changed'

} ElseIf ((Get-ItemProperty -Path $RegKey -Name $RegName -ErrorAction SilentlyContinue).$RegName -ne $RegValue) {
	Write-Verbose -Message "$BlockLocation Registry key and value already exist, but the value is not correct."
	Write-Verbose -Message "$BlockLocation Correcting the Registry value."
	[System.Void](New-Item $RegKey -Force | New-ItemProperty -Name $RegName -Value $RegValue -Force)
	$CheckChange = $true
	$FixType = 'corrected'

} Else {
	Write-Verbose -Message "$BlockLocation Registry key and value already exist, and the value is correct."
}
#endregion

So, there’s that: My Write-Verbose best practice. While it wasn’t a stupid draft so much, there’s one less in the drafts!

An Advanced Function Template (Version 2.1 -and -gt)

Version 2.1

Back in October 2017, I spoke at the Arizona PowerShell Saturday in Phoenix, Arizona. My topic, was introducing those that attended, to a Windows PowerShell Advanced Function Template. I found a flaw in it which I’ve since fixed in my 2.1 version linked below. For anyone after this template, this is the most current version. Newer versions, providing there are any will be included in this post and not in another, separate post around here. Therefore, 2.1 may not forever be the current version, but the current version will always be a part of this post.

The flaw that took us 2.1 was that all parameters and parameter values are logged. That’s a good thing, but it means that if someone supplied a password as a string, it would be logged (providing the Log parameter was used). Therefore, 2.1 looks for parameter’s with the word “password” in them, and then doesn’t include the parameter value. Instead, it would read like the below example. This isn’t fool proof, but I think it’s a helpful addition.

PS > Get-CPUCount -ComputerName server01 -Password 'test1' -MainPassword 'test2' -PasswordName 'test3' -xPasswordx 'test4' -Log ToScreen
...
[1/10/2018 10:03:27 PM]: [INFO   ] Including the "ComputerName" parameter with the "server01" value.
[1/10/2018 10:03:27 PM]: [INFO   ] Including the "Password" parameter without the parameter value.
[1/10/2018 10:03:27 PM]: [INFO   ] Including the "MainPassword" parameter without the parameter value.
[1/10/2018 10:03:27 PM]: [INFO   ] Including the "PasswordName" parameter without the parameter value.
[1/10/2018 10:03:27 PM]: [INFO   ] Including the "xPasswordx" parameter without the parameter value.
[1/10/2018 10:03:27 PM]: [INFO   ] Including the "Log" parameter with the "ToScreen" value.
...
AdvancedFunctionTemplate2.1 (4058 downloads )

 

Older Posts:

An Advanced Function Template (Version 2.1 -and -gt)

An Advanced Function Template (Version 2.1 -and -gt)

An Advanced Function Template (2.0 Version)

Welcome. If you’re here for the download, it’s toward the bottom of this post.

Today’s post goes hand in hand with a session I gave at the Arizona PowerShell Saturday event on Saturday, October 14, 2017. I didn’t do it previously, but this year especially, it made sense to have a post at tommymaynard.com, as a part of my session at the event. I wanted a place to offer my advanced function template for download, and so this, is it. If you couldn’t attend the event and be a part of the session yourself, then this may be the next best alternative. Well, for my session anyway. This event included sessions from Jason Yoder, Will Anderson, and Jason Helmick. While we’re at it — naming names — many thanks to Thom Schumacher for his role in organizing this event.

Toward the end of 2016, I spent some nights and weekends, and moments in the office too, writing a PowerShell advanced function template. Its main purpose was to include built-in function logging. You see, I wanted logging, but I didn’t want an external logging function to do it, and so I decided that every one of my functions would use the same template, and therefore, I could offer consistent logging capabilities across all my functions. These include my own functions, and even those written and put in place for my coworkers. At last check, I’ve snuck 40 plus tools into production. These include PowerShell functions for Active Directory, Group Policy, Exchange, SharePoint, Office 365, Amazon Web Services, VMware, and general operating system and management needs. There’s always something to automate, and now, when they do get automated, each includes the same base functionality.

I liked it, I use it, and I even made my advanced function template available for download on its original post. After some use, I came to realize that it could’ve been better. If you’ve been at this scripting and automation game for awhile, then you understand that automation, even when it’s done, is never really done. There’s always room for improvement, even if there isn’t always time to execute that mental list of changes, fixes, and increased functionality you want to add to already written automation.

The first thing I needed, which I didn’t even know I needed at first, was a function, and I’m not talking about the template code. I’m talking about a way to demonstrate both advanced function template versions (1.0 and 2.0), using the same non-template code. At first, I thought I’d just walk though the code in my 2.0 version of my advanced function template, but really, it made sense to use an easy to understand previously written function as an example, running in both the 1.0 and 2.0 versions of my advanced function template. At nearly the same time I was prepping for PowerShell Saturday, I had written a function that created random passwords — I know, I know… there’s a bunch of these already. I took that function’s code and wrapped it in my 2.0 version, as it had already been written with my 1.0 version, for use in my session at the PowerShell Saturday event.

All the files I used, are included in the below, free from viruses, zip file. This includes the ArizonaPowerShellSaturday.ps1 file that I used to run all the various commands, the New-RandomPassword1.0.ps1 file (uses the 1.0 version of advanced function template), and New-RandomPassword2.0.ps1 file (uses the 2.0 version of advanced function template), and the blank AdvancedFunctionTemplate2.0.ps1 — this is the one you’re likely after. If you attempt to use the first file mentioned, ArizonaPowerShellSaturday.ps1, then you’ll need to modify the first region, where the variables are assigned, so that they point to the other three files, wherever you decided to save them. Also, there’s a couple references to an alias I use, called code. I don’t believe this is a built-in alias, so the line won’t work as expected on other people’s systems. Know that the idea behind those lines is to open the referenced file inside of Visual Studio Code.

ArizonaPowerShellSaturday2017AllFiles (4146 downloads )

Update: I was asked at the PowerShell Saturday event, what kind of license I had. Ugh, none. But, for the sake of those that need it, let’s distribute this under the MIT License further below.

Update: The built-in ability to do logging that’s in my Advanced Function template writes all parameter names and associated values to the screen, file, or to both the screen and file. This means that if you’re passing secure data as a parameter value, that it needs to be done in a secure manner, or it’s going to appear in the logs. I intend to put in a stopgap for this, but it may not be perfect. You can read more here: http://tommymaynard.com/potentially-avoid-logging-plain-text-passwords-2017. Watch for a link on this post, and that one, to the newest post that’ll include the 2.1 versions!

Update: And, here’s the link!

An Advanced Function Template (Version 2.1 -and -gt)

Copyright 2017

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.