Skip to content



I'd read about Terraform 0.12 and thought it was much further out, so moved on with regret from evaluating the massive number of improvements. Just found out it was released, and choco upgrade terraform -y provided me with a delightful 0.12 upgrade. If you haven't explored it yet, go do it!

Things like loops, no longer having to reference any variable with string interpolation, and more promises to make this a big productivity improvement for those enjoying Terraform.

If you aren't using any Infrastructure-As-Code approach right now, you'll find it initially a little confusing, but get past that and you'll wonder how you ever lived without it.


Really enjoying my experience with Terraform from the last month. If you have any resources in the cloud you have to deploy and you are having to do them manually, you should take a look. It's very easy to get going with the basics and the time it can save as you build up Terrachops (patent pending) can be tremendous.


Kids learn so quick. It's amazing how fast my son has picked up #origami. He often has to wait for his slow-poke dad.

origami with son


Downloading a Visual Studio Code vsix extension and then installing manually in Azure Data Studio works for some extensions! For instance, downloading Simple Alignment from the marketplace, and then running in Azure Data Studio successfully installed the utility.

You can also many of your keyboard settings straight from vscode into Azure Data Studio so you don't have to remap all those keys. The beauty of json configuration files 😄

2019-04-25 00:39:35 -0500

Finally upgraded my graphics card and hardrive. EVGA RTX 2080 and a Samsung Evo 970 NVMe 2TB SSD breathed new life into my PC. Running 100hz 3440x1440 ultrawide on a 10 year old AMD 6950 was just not doing the trick.

2019-04-19 22:57:00 -0500

Working smart: how great performers do less work

This book so far is a good read. I like the concept of the "feedback loop", and doing small iterative improvements with a targeted narrow focus to improve each day. It's very much in alignment with Agile concepts. It's kinda like delivery of small measurable bits of value for your own improvement. This contrasts our typical promise to ourselves of radical transformation or resolutions that never get realized. Clarifying these concepts is really helpful to help one be proactive instead of reactive about personal growth.


This infernal keyboard lag is killing me. I'm guessing it's due to some OS corruption as my disk is going bad and bad sector warnings keep increasing. Blah! 😬


Today I learned how to create a microblog / microblogs section in my hugo layout by frakensteining together some styling tweaks to part of hugo theme. 🌮

Debugging Type Binding in PowerShell

Some effort I spent in researching Type Binding in Stack Overflow to help answer a question by Chris Oldwood helped me solidify my understanding of the best way to debug more complicated scenarios such as this in PowerShell.

Why does this PowerShell function's default argument change value based on the use of . or & to invoke a command within it?

Spent some digging into this and this is what I've observed.

First for clarity I do not believe that you should consider the NullString value the same as null in a basic comparison. Not sure why you need this either, as this is normally something I'd expect from c# development. You should be able to just use $null for most work in PowerShell.

if($null -eq [System.Management.Automation.Language.NullString]::Value)
    write-host "`$null -eq [System.Management.Automation.Language.NullString]::Value"
    write-host "`$null -ne [System.Management.Automation.Language.NullString]::Value"

Secondly, the issue is not necessarily because of the call operator, ie &. I believe instead you are dealing with underlying parameter binding coercion. Strong data typing is definitely a weak area for PowerShell, as even explicitly declared [int]$val could end up being set to a string type by PowerShell automatically in the next line when writing Write-Host $Val.

To identify the underlying behavior, I used the Trace-Command function (Trace Command) .

I changed the Use-Dot to just call the function as no write-host was needed to output the string.

function Use-Ampersand
        [string]$NullString = [System.Management.Automation.Language.NullString]::Value
    Format-Type $NullString
    &cmd.exe /c exit 0

The Format-Type I modified to also use what is considered a better practice of $null on the left, again due to type inference.

function Format-Type($v= [System.Management.Automation.Language.NullString]::Value)

    if ($null  -eq $v)
    else {

To narrow down the issue with the data types, I used the following commands, though this is not where I found insight into the issue. Theyh when called directly worked the same.

Trace-Command -Name TypeConversion -Expression { Format-Type $NullString} -PSHost
Trace-Command -Name TypeConversion -Expression { Format-Type ([System.Management.Automation.Language.NullString]$NullString) } -PSHost

However, when I ran the functions using TypeConversion tracing, it showed a difference in the conversions that likely explains some of your observed behavior.

Trace-Command -Name TypeConversion  -Expression { Use-Dot} -PSHost
Trace-Command -Name TypeConversion  -Expression { Use-Ampersand} -PSHost
DEBUG: TypeConversion Information: 0 :  Converting "" to "System.String".
DEBUG: TypeConversion Information: 0 :      Converting object to string.
DEBUG: TypeConversion Information: 0 :  Converting "" to "System.Object". <<<<<<<<<<<
DEBUG: TypeConversion Information: 0 :  Converting ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW;.CPL" to "System.String".
DEBUG: TypeConversion Information: 0 :      Result type is assignable from value to convert's type

OUTPUT: (null)

# Use-Ampersand
DEBUG: TypeConversion Information: 0 : Converting "" to "System.String".
DEBUG: TypeConversion Information: 0 :     Converting object to string.
DEBUG: TypeConversion Information: 0 : Converting "" to "System.String". <<<<<<<<<<<
DEBUG: TypeConversion Information: 0 :     Converting null to "".        <<<<<<<<<<<
DEBUG: TypeConversion Information: 0 : Converting ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PY;.PYW;.CPL" to "System.String".
DEBUG: TypeConversion Information: 0 :     Result type is assignable from value to convert's type

OUTPUT: System.String

The noticeable difference is in Use-Ampersand it shows a statement of Converting null to "" vs Converting "" to "System.Object".

In PowerShell, $null <> [string]''. An empty string comparison will pass the null check, resulting in the success of outputting GetType().

A Few Thoughts On Approach With PowerShell

Why it's doing this, I'm not certain, but before you invest more time researching, let me provide one piece of advice based on learning the hard way.

If start dealing with issues due to trying to coerce data types in PowerShell, first consider if PowerShell is the right tool for the job

Yes, you can use type extensions. Yes, you can use .NET data types like $List = [System.Collections.Generic.List[string]]::new() and some .NET typed rules can be enforced. However, PowerShell is not designed to be a strongly typed language like C#. Trying to approach it like this will result in a many difficulties. While I'm a huge fan of PowerShell, I've learned to recognize that it's flexibility should be appreciated, and it's limits respected.

If I really had issues that required mapping [System.Management.Automation.Language.NullString]::Value so strongly, I'd consider my approach.

That said, this was a challenging investigation that I had to take a swing at, while providing my 10 cents afterwards.

Other Resources

After posting my answer, I found another answer that seemed relevant, and also backs up the mentioning of not using [NullString] normally, as its usage in PowerShell is not really what it was designed for.

Stackoverflow specific content republished under CC-BY-SA

Last Minute Migration?

If you are about to perform a last minute migration here's a couple tips as you jump ship from Hipchat and move to Slack. Hipchat is sunsetting I believe on Feb 15th, so I figured I'd share what I do have in case it's helpful, as it won't stay tremendously relevant for long.

Problem: Hipchat access will be removed and you need more time

Export the hipchat content to a file and upload to your own s3 bucket. That will ensure you have some time to work through the migration and reverse it and try again if you aren't happy with the results.

Problem: You want to do an initial import of Hipchat content and then update with deltas.

Don't even consider it. The slack import can't add delta content for private messages and private rooms. This means you'd get a lot of duplicate rooms being created. It's better to do the migration import in one batch rather than try to incrementally pull in content. Don't go down this route, as I didn't discover this till later in the process resulting in a time-crunch.

Problem: You have hipchat users that have email address that have been migrated to a new domain since they were created.

You've migrated to a new domain, but your Hipchat accounts all have the previous email which you've setup as email aliases. You can't easily change in Hipchat due to the fact it's set a profile level, "synced" to the Atlassian account. I had no luck in working on changing this so I instead leveraged the Slack API to bulk update during migration (after all the accounts were created). I mapped the active directory user to the current user by parsing out the email aliases and reversing this. I also created an alternative approach for those that had no matching email alias, and iffy full name matching to use fuzzy matching based soley on last name in the email address.

Improving Your Migration Experience

Rename Your Hipchat Rooms Prior to Migration (optional)

The Slack Migration tool is pretty good, but the auto renaming had some rename behavior that didn't align in a clean manner with what my naming convention was going to be. This means to simplify your migration, it's better to rename your Hipchat rooms prior to migration so all your rooms now create slack channels that don't have to be renamed again. Also, if you pull in a delta of content for public rooms, it can automatically match and incrementally add content (this doesn't work for private content).

Getting Started with Hipchat CLI

It's painful. Hipchat's going into the great beyond so don't expect support for it.

{{< admonition type="warning" title="Important" >}} API Key for personal won't access full list of rooms in the action getRoomList in the CLI. Instead, you'll need to obtain the room list using Add-On token which I found too complex for my one time migration. Instead, you can copy the raw html of the table list, and use a regex script to parse out the room name and number list and use this. You can still perform room rename, just not sendmessage action on the rooms using the API token. {{< /admonition >}}

  1. Install integration from marketplace to the entire account
  2. Download the CLI for running locally
  3. Create API Key. Important. This is a 40 character personal key, not the key you create as an admin in the administrators section. You need to go to your personal profile, and then create a key while selecting all permissions in the list to ensure full admin privileges.
  4. To get the raw HTML easily, simply try this Chrome extension for selecting the table and copying the raw html of the table. CopyTables
  5. Open the room listing in Hipchat. Using the extension select Rows as your selection criteria and then select Next Table. Copy the Raw html to an empty doc. Go to the next page (I had 3 pages to go through) and copy each full table contents to append to the raw html in your doc.
  6. Once you have obtained all the html rows, then run the following script to parse out the html content into a [pscustomobject[]] collection to work with in your script.
$HtmlRaw = Get-Content -Path '.\TableRowRawHtml.html'
$Matched = Select-String -InputObject $HtmlRaw -Pattern '((?<=rooms/show/)\d*(?="))(.*?\n*?.*?)(?<=[>])(.*?(?=<))' -AllMatches | Select-Object -ExpandProperty Matches

Write-PSFMessage -Level Important -Message "Total Match Count: $(@($Matched).Count)"

[pscustomobject[]]$RoomListing = $Matched | ForEach-Object -Process {
    $m = $_.Groups
            RoomId           = $m[1].Value
            OriginalRoomName = [system.web.httputility]::HtmlDecode($m[3].Value)

Write-PSFMessage -Level Important -Message "Total Rooms Listed: $(@($RoomListing).Count)"

Now you'll at least have a listing of room id's and names to work with, even if it took a while to get to it. There are other ways to get the data, such as expanding the column-format=999 but this timed out on me and this ended actually being the quickest way to proceed.

Using CLI

To get started, cache your credentials using the fantastic BetterCredentials module. To install you'll need to run Install-Module BetterCredentials -Scope CurrentUser -AllowClobber -Force

Then set your cached credentials so we don't need to hard code them into scripts. This will cache it in your Windows Credential manager.

$cred = @{
    credential   = ([pscredential]::new('myHipchatEmail' , ("APITokenHere" | ConvertTo-SecureString -AsPlainText -Force) ) )
    type         = 'generic'
    Persistence  = 'localcomputer'
    Target       = 'hipchatapi'
    description  = 'BetterCredentials cached credential for hipchat api'
BetterCredentials\Set-Credential @cred

Initialize the working directory and default parameters for the CLI so you can easily run other commands without having to redo this over and over.

#                 set location for the java cli environment                  #
$Dir = Join-Path 'C:\PathToCli' 'atlassian-cli-8.1.0-distribution\atlassian-cli-8.1.0'
Set-Location $Dir
$Url = ''

#              configure default arguments for calling java cli              #
$JavaCommand = "java -jar $(Join-Path $dir 'lib/hipchat-cli-8.1.0.jar') --server $url --token $Password --autoWait --quiet"

Now you can issue some simple commands to start manipulating the CLI.

#          Get Entire Room Listing -- Including Archived & Private           #
$Action = '--action getRoomList --includePrivate --includeArchived --outputFormat 1'
$result = Invoke-Expression -command "$JavaCommand $Action"
$RoomList = $result | ConvertFrom-CSV
$RoomList | Export-CliXml -Path (Join-Path $ScriptsDir 'CurrentRoomList.xml') -Encoding UTF8 -Force #just so we have a copy saved to review

I just tweaked this snippet for other types of commands, but this should get you pretty much what you need to run interactive commands via CLI. I've also written up some Slack functions and will likely share those soon as well as I've found them helpful in automatically fixing email addresses, activating & deactivating users, identifying active billed users, and other basic administrative focused actions.