Hash Table to CSV, Revisited

I started writing about PowerShell in June 2014 at tommymaynard.com. While I’m not a Microsoft MVP, I’m certainly working to obtain Microsoft’s longevity award. While the distinction doesn’t actually exist, this year will mark six consistent years of reading, researching, spending my time with PowerShell, and giving back to the community in my written word. With the combination of my site and this one, I’ve authored somewhere close to 350+ articles, or posts.

What I’m really out to say here, is that in my time, I’ve noticed which of my writings have been consistently popular. There’s a particular post on my site that sees the largest amount of consistent visitors: It’s Hash Table to CSV. I’ve been looking for a way to take its personal success and provide more to the reader about the topic. That’s what we’re doing today with this post. I put a link to the original post on Twitter recently, and someone provided me an idea for an update — whether they realized it or not. It’s simple, but whatever. As proven, those are sometimes the most popular posts.

Before we get back into this, let’s remember how this all started. It started here: Hash Table to CSV. It’s always been a popular post, but just in case you didn’t follow this over from PowerShell.org, then you need to know this is a forward movement on that 2018 article.

I was sitting around and wondered how to best get a hash table into a CSV file. Maybe it was for configuration; I still don’t recall. Well, my option wasn’t the only option, and so today I’ll pass along something someone on Twitter pointed out that I hadn’t considered myself. We can use [PSCustomObject], introduced in (Windows) PowerShell 3.0.

I’ve long used this to create custom objects for its speed and insertion order preservation (over New-Object), but it didn’t occur to make use of it when getting a hash table into a CSV. It does work, with less work, so let’s try it on. While I won’t show the previous option, revisit the first article on this topic to see the old way; here’s the new way. We’ll start with our previously used hash table.

$HashTable = @{
    Name = 'Tommy'
    StreetName = 'Vista Pl.'
    City = 'Tucson'
}

$HashTable
Name                           Value
----                           ----- 
Name                           Tommy
StreetName                     Vista Pl.
City                           Tucson

Once that’s populated, instead of using the hash table’s GetEnumerator method, we’ll use [PSCustomObject]. This converts each entry in our hash table into an object, and sends the object to the Export-Csv cmdlet. We get what we expect in our CSV file without the additional work scripted into the first hash table to CSV article from 2018.

[PSCustomObject]$HashTable |
    Export-Csv -NoTypeInformation -Path .\Desktop\NewHashToCsv.csv

And, that was it. Quicker, easier, and more likely to be remembered, long term.

Edit: It was pointed out to me that this didn’t produce the exact information in the CSV from 2018. Instead of having a Key and Value properties, it included a property/column for each of the hash table’s keys: Name, StreetName, and City. Honestly, I think I like this better, as it reminds me of what any command that returns objects in PowerShell would do for us. If I were to read it back into the host (via Import-Csv), my output from the operation would be more in line with what PowerShell commands produces anyway: objects with properties. Thank you for the observation, Mike!

PowerShell Function with a Function Full of Functions

Yeah, you read that title correctly. Or did you? You might want to double-check; I had to write it a few times.

I did that thing where I authored some PowerShell code and I don’t know what to do with it. While it was written during some updates to a common function template, I’m not yet sure it’ll be implemented there. Therefore, I figured I’d share it. Additionally, this will give me a place to store it, in case I do use it. Some day you may ask yourself, can I create a function that creates a global function, that contains other functions? Yes. Today, I intend better help answer that question and include an example.

All this said I’m not here to explain why you might want to do this. Again, I’m not even sure if I’ll need this. So Internet and you, it’s yours. Edit: Some time has passed since I wrote this opening, so yes, I’ll be using this in production.

I have a function template that I use when writing any new function(s). Its number one goal is to ensure that all my functions, or tools, include identical logging. It’s identical, whether it’s written to the screen, to a file, or to both. I feel like I’ve written that before.

When the function is complete, and it’s written a log file, I have a global function that is created, too. This allows you to quickly open the log file, open just the location of the log file, or read in the contents of the most recently created log file and create an object from each line. If a single function is created, then the user isn’t required to remember more than a single function name. Sure, they might have to remember the parameter values for the single functi– okay, no they wouldn’t. They can tab through those.

Code Example

Function Show-FunctionTemplate {
    'You''re inside the Function Template (1st).'

    Function Global:New-FtFunction {
        Param (
            [Parameter()]
            [ValidateSet('1','2','6')]
            [array]$Function
        ) # End Param.

        Switch ($Function) {
            '1' {Function Test-One {'Test-One'}; Test-One}
            '2' {Function Test-Two {'Test-Two'}; Test-Two}
            '6' {Function Test-Six {'Test-Six'}; Test-Six}
        } # End Switch.
    } # End Function: New-FtFunction.

    'You''re inside the Function Template (2nd).'
} # End Function: Show-FunctionTemplate

Show-FunctionTemplate

New-FtFunction
New-FtFunction -Function 1
New-FtFunction -Function 1,2
New-FtFunction -Function 1,2,6

Discussion

The overall goal in this code is that we’ve created a function named Show-FunctionTemplate, in our local, and in this case, global scope. Done.

Inside this function (when it’s invoked), we echo a couple of statements about being inside the function. Additionally, and in between those two statements, we create a second global function named, New-FtFunction. When Show-FunctionTemplate is done executing, New-FtFunction can then be invoked.

When the New-FtFunction is invoked it won’t do anything unless one of the parameter values that it’s expecting is included. In the above code sample, I’ve included some examples. The below image shows the output returned from executing these.

Output of the New-FtFunction function.

Perhaps it should be noted, but the most nested of functions, both define the function and invoke it — one right after the other. But in obvious news now, I can do it. I can make a (global) function, make a global function responsible for making and invoking other functions. I think this is going into my function template. I really do. Edit: It did.

PowerShell’s Get-Date FileDateTime for Safe Filenames

Since about the beginning of time now, I’ve used Get-Date to create my own safe filenames. It’s a date and time format that is safe to be used as/in a file’s name without including anything that can’t be. Here’s what I’ve long used.

Get-Date -Format 'DyyyyMMddTHHmmss.fffffff'

That’ll produce something such as this.

D20191211T201315.1474900

Okay, maybe I haven’t always used this, but I certainly used a variation. It was after the beginning of time, if you will, that I switched to this version, which includes the dot and the seven Fs. The Fs (in lowercase [it’s important]) display milliseconds. I added these to help ensure that filenames that include the date and time were more likely to be unique. You can imagine. You create more than one file in the same second, and you run up against a file already existing error if you don’t include milliseconds. Before we continue, let’s ensure all the other letters represented in the above code example are included here:

I should mention, that using this format ensures proper file sorting and ordering. That can be important. It’s always been there (or it’s likely, at least), but it turns out that there’s an easier way than what I’ve been doing.

I can still learn something new that I hadn’t known before, such as using Get-Date differently. I can now create safe filenames that include the date and time, even after I’ve spent several dedicated years of using and writing about PowerShell. Maybe it was forgotten. I doubt that in this case. That said, it is possible to overlook easier, and potentially better ways of doing things once you have a solution for something.

Instead of creating safe filenames using Get-Date, the Format parameter, and .NET format specifiers (as they’re called), I recently noticed that someone — likely at Microsoft, as this has probably been around a while — took care of this for us.

The Get-Date cmdlet includes a couple, notable parameter values that can be used with the Format parameter. Two we’ll discuss are FileDate and FileDateTime. Take a look at this; it returns the date, without the time.

PS> Get-Date -Format FileDate
20191211

The date without the time is not exactly what I was after — the time is vital in my mind. Therefore, let’s repeat this example with the FileDateTime Format parameter value.

PS> Get-Date -Format FileDateTime
20191211T2015018186

Oh, look — there it is That’s the built-in way to ensure a unique and safe filename that includes date and time without the need of using my earlier example. It’ll probably be easier to remember that than this value: ‘DyyyyMMddTHHmmss.fffffff’. Do notice that the decision at Microsoft, because again this was probably written before PowerShell was open-sourced, is that four digits for milliseconds is likely suitable. I’m even not sure why I chose to use seven digits, anyway. Meh.

And, if you want to find out about uniqueness for sure — as I did — put it in a loop. The below example creates 25 safe (files and) filenames. It seems to work just fine for me.

PS> 1..25 | ForEach-Object {New-Item -Path '.\Documents\Test' -Name "$(Get-Date -Format FileDateTime).txt"}

    Directory: C:\Users\tommymaynard\Documents\Test

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        12/11/2019  8:30 PM              0 20191211T2030184424.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184471.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184486.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184507.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184521.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184535.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184548.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184561.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184577.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184593.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184614.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184630.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184652.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184669.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184687.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184704.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184721.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184738.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184759.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184784.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184804.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184850.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184866.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184883.txt
-a----        12/11/2019  8:30 PM              0 20191211T2030184899.txt

Not one naming collision using only four milliseconds. I’m sold. I won’t ever forget you FileDateTime.

A PowerShell Recursive Function

Note: There is a newer post on tommymaynard.com about a recursive function I wrote. When you are done reading this post, read that one.

I was recently in a position where I was asked to provide an example of a PowerShell recursive function. Unfortunately, I didn’t have time to do that as it was a part of a written and timed test. Sadly, I didn’t even really have time to review the other 50-some answers I had already provided. I mostly knew that might happen.

Anyway, the challenge has been eating at me, so why not write an example and share it. It’ll be good for you, as it will be for me too. It’s out there, but it’s not often utilized that I’m aware. A recursive function is a function that calls, or invokes, itself. For real. We’re not referring to a function, or cmdlet, that includes a Recurse parameter. Now that said, it’s possible that some of these commands, with that parameter, do this (call themselves), but I’ve yet to take a look to see for sure.

After you see today’s example, you’ll begin to understand why you might prefer to do things this way. If you can wrap your head around the concept, you’ll understand that it can reduce a portion of code writing on your part.

As stated, “A recursive function is a function that calls, or invokes, itself.” On that note, let’s first set up the folder structure necessary for this example. While you can do this with PowerShell, I simply didn’t bother. As you can see below, there’s a “Test” folder inside my “Documents” folder. That folder contains three files — A, B, and C.ps1. Additionally, it contains a nested, “More” folder, which contains three more files — D, E, and F.ps1.

So there’s that. Now, let’s take a look at and discuss our PowerShell recursive function.

Function Get-Ps1File {
    Param ($Path= '.\')

    Get-ChildItem -Path $Path | ForEach-Object {
        If ($_.Name -like "*.ps1") {
            $_.Name
        } ElseIf ($_.PSIsContainer) {
            Get-Ps1File -Path $_.FullName
        } # End If-ElseIf.
    } # End ForEach-Object.
} # End Function: Get-Ps1File.

This function loops its way through a path and returns the file(s) that are .ps1 files. If there’s a nested folder, it then goes into that folder and runs itself again (against the content of that folder). Knowing what we know about this folder structure and its files, the below results returned from running this function should make perfect sense.

Get-Ps1File
D.ps1
E.ps1
F.ps1
A.ps1
B.ps1
C.ps1

Let’s recap on this a bit with a little more detail. When the function is first invoked, it uses the current path, as none was supplied. It then runs the ForEach-Object cmdlet against the files in the base folder. Using the If portion of the If-ElseIf statement inside the loop, it determines it should return the A, B, and C .ps1 files. Then, on the fourth iteration through the loop, it hits our “More” folder and determines it’s a folder. There are other ways to determine if it’s a folder, but I stuck with the tried and tested, PSIsContainer NoteProperty.

At this point, it invokes itself against the path of the “More” folder. It runs or executes itself, again. This time, however, it uses the folder’s FullName property (its full path). Inside that second invocation of the function, it iterates over the files inside the “More” folder, echos them to the screen as output, and then ends the function’s second invocation. At that point, it returns back to the first invocation, where it then ends that invocation, as well, as its work is completed.

If you’d prefer to see this sorted, pipe the function name (Get-Ps1File) to the Sort-Object cmdlet.

Get-Ps1File | Sort-Object
A.ps1
B.ps1
C.ps1
D.ps1
E.ps1
F.ps1

And there you go, an example of a PowerShell recursive function. You may not need it often, but you may need it someday.

 

External New Posts – 11/2019

This was a first — I didn’t write any new PowerShell content in November. It’s the first time since June 2014 where this happened. It feels weird.

All that said, I did do something. I took some “free” time to help at Adam the Automator in November, to help edit some of their newest content. They’re producing some great, highly in-depth articles, so do be certain to make it a part of your continued, industry learning. It’s not just about PowerShell, which can be okay sometimes. You love it (or secretly hate it) and I love it too, but there’s more than PowerShell to know, to maintain our relevancy.

External New Posts – 09/2019

NoticeThe below links originally led to content on an external website. The links are no longer valid; therefore, work is being put in to restore this externally published content, on tommymaynard.com. These links will be updated as soon as it is possible.

September 2019
AWS UserData Multiple Run Framework Part IV (a) – September 12, 2019
AWS Vendor-Written Generated Code – September 13, 2019
AWS UserData Multiple Run Framework Part IV (b) – September 13, 2019
Read-Only and Constant Variables – September 23, 2019