Tuesday, March 27, 2018

Get weird time zones in PowerShell

There are weird time zones where the offset is not a round number of hours. If you are wondering where they are, we can find out with a single PowerShell line.

Windows has to work anywhere. So Microsoft keeps it up to date with all of the various time zones in the world. A “time zone” is not just the offset from UTC; it also includes the local rules for daylight saving time. As of this writing, there are 136 different time zone definitions recognized by Windows.

Windows stores these settings in the registry, but we can most easily get them using the .GetSystemTimeZones() static method of the [System.TimeZoneInfo] object.

[System.TimeZoneInfo]::GetSystemTimeZones()

This results in an array of TimeZoneInfo objects.

The TimeZoneInfo property .BaseUtcOffset holds the information we are looking for today. We only want those time zones with an odd offset. .BaseUtcOffset is a [TimeSpan] object. TimeSpan objects have a property called .TotalMinutes. We want those time zones whose .BaseUtcOffset is not a multiple of 60.

We can test an offset by using the % modulus operator to get the remainder of a division. (For math geeks, the % operator returns a remainder, not a modulus. For computer geeks, you can go back to thinking these are the same thing.)

When converting an integer to a [Boolean] $True or $False, zero converts to $False, and everything else converts to $True.

We combine these facts to create a filter that gives us only the time zones with unusual offsets.

[System.TimeZoneInfo]::GetSystemTimeZones() |
    Where-Object { $_.BaseUtcOffset.TotalMinutes % 60 }

And then we select specific properties to make the list more readable.

[System.TimeZoneInfo]::GetSystemTimeZones() |
    Where-Object { $_.BaseUtcOffset.TotalMinutes % 60 } |
    Select-Object -Property IdBaseUtcOffset

See also
It’s always 5 o-clock somewhere: Using .Net and PowerShell’s Extended Type System to find out where

Saturday, March 3, 2018

Leveraging dynamic type conversion in PowerShell - Part 1 - Walkthrough of a crazy example

Dynamic type conversion can be leveraged to create more efficient, intuitive PowerShell code, scripts that are easier to write, easier to read, and easier to maintain. You are already using it without realizing it. Better understanding dynamic type conversion and some of the tricks related to it means you can use it intentionally to improve your code.

Dynamic type conversion means we can use the wrong thing in an expression, and instead of throwing an error like any other language would do, PowerShell will automatically convert it into the right thing on the fly. PowerShell has over 16,000 lines of source code it uses to figure out how to perform a given conversion.

Let’s start by analyzing a crazy example that makes liberal use of dynamic type conversion tricks to make code that is almost impossible to write and read.

The journey begins in crazy town

Thomas Rayner, the Working Sysadmin, wrote this brilliant piece of obfuscated code.

$__="$(@{})";$__[-((!@()+!@())*(!@()+!@())*(!@()+!@())+(!@()+!!@()))]+$__[-(!@()+!!@())]+$__[-(!@()+!@())]+$__[-(!@()+!@())]+$__[(!@()+!@())*(!@()+!@())*(!@()+!@())]

Without running it, it is almost impossible to figure out what that does. If you do run it, you can see the end result, but probably can’t see how it does it.

Let’s figure it out.

First, it appears to be two commands, separated by a semicolon. Let’s split them onto separate lines.

$__="$(@{})"
$__[-((!@()+!@())*(!@()+!@())*(!@()+!@())+(!@()+!!@()))]+$__[-(!@()+!!@())]+$__[-(!@()+!@())]+$__[-(!@()+!@())]+$__[(!@()+!@())*(!@()+!@())*(!@()+!@())]

The dollar sign and double underscore is just an odd variable name.

Let’s change $__ to $X to make it easier to read.

$X="$(@{})"
$X[-((!@()+!@())*(!@()+!@())*(!@()+!@())+(!@()+!!@()))]+$X[-(!@()+!!@())]+$X[-(!@()+!@())]+$X[-(!@()+!@())]+$X[(!@()+!@())*(!@()+!@())*(!@()+!@())]

It’s a bit more apparent now that the first line assigns a value to $X, and the second line adds several $X things together.

Let’s add white space to make that even more apparent.

$X = "$(@{})"
$X[-((!@()+!@())*(!@()+!@())*(!@()+!@())+(!@()+!!@()))] + $X[-(!@()+!!@())] + $X[-(!@()+!@())] + $X[-(!@()+!@())] + $X[(!@()+!@())*(!@()+!@())*(!@()+!@())]

We can take that a step further. Rather than have it run off the side of the screen, we can break it into multiple lines. In PowerShell we can add a new line after anything that syntactically must be followed by another thing. So we can add a new line after each plus sign.

$X = "$(@{})"
$X[-((!@()+!@())*(!@()+!@())*(!@()+!@())+(!@()+!!@()))] +
    $X[-(!@()+!!@())] +
    $X[-(!@()+!@())] +
    $X[-(!@()+!@())] +
    $X[(!@()+!@())*(!@()+!@())*(!@()+!@())]

Now it’s easier to see each $X thing. It looks like each $X thing has some adding and multiplying going on. Let’s add more white space to see that better.

$X = "$(@{})"
$X[-( ( !@() + !@() ) * ( !@() + !@() ) * ( !@() + !@() ) + ( !@() + !!@() ) )] +
    $X[-!@() + !!@() )] +
    $X[-!@() + !@() )] +
    $X[-!@() + !@() )] +
    $X[!@() + !@() ) * ( !@() + !@() ) * ( !@() + !@() )]

An exclamation point is the equivalent of -not. Programmers from other languages are used the exclamation point in this context, but in PowerShell we usually use -not, because it is easier to intuitively read and understand.

So replace all of the ! with -not.

$X = "$(@{})"
$X[-( ( -not @() + -not @() ) * ( -not @() + -not @() ) * ( -not @() + -not @() ) + ( -not @() + -not -not @() ) )] +
    $X[--not @() + -not -not @() )] +
    $X[--not @() + -not @() )] +
    $X[--not @() + -not @() )] +
    $X[-not @() + -not @() ) * ( -not @() + -not @() ) * ( -not @() + -not @() )]

So far we haven’t changed anything. We just made it easier to read. Now we can begin to parse it.
Here’s the first dynamic type conversion. The -not operator can only take a [Boolean] $True or $False as input. If we give it a different type of input, PowerShell converts the input to a [Boolean] before applying the -not.

For arrays, a non-empty array converts to $True, and an empty array converts to $False. So let’s replace all of the empty arrays with $False.

$X = "$(@{})"
$X[-( ( -not $False + -not $False ) * ( -not $False + -not $False ) * ( -not $False + -not $False ) + ( -not $False + -not -not $False ) )] +
    $X[--not $False + -not -not $False )] +
    $X[--not $False + -not $False )] +
    $X[--not $False + -not $False )] +
    $X[-not $False + -not $False ) * ( -not $False + -not $False ) * ( -not $False + -not $False )]

-Not $False, of course, converts to $True. Let’s make that replacement.

$X = "$(@{})"
$X[-( ( $True + $True ) * ( $True + $True ) * ( $True + $True ) + ( $True + -not $True ) )] +
    $X[-$True + -not $True )] +
    $X[-$True + $True )] +
    $X[-$True + $True )] +
    $X[$True + $True ) * ( $True + $True ) * ( $True + $True )]

There were two -not -not $False, which are now -not $True. We can replace those with $False.

$X = "$(@{})"
$X[-( ( $True + $True ) * ( $True + $True ) * ( $True + $True ) + ( $True + $False ) )] +
    $X[-$True + $False )] +
    $X[-$True + $True )] +
    $X[-$True + $True )] +
    $X[$True + $True ) * ( $True + $True ) * ( $True + $True )]

Here is the second dynamic type conversion. When we add two things that don’t otherwise make sense to add, PowerShell will convert them to numbers, if it can.

For Boolean values, $True converts to 1, and $False converts to 0. Let’s do that conversion.

$X = "$(@{})"
$X[-( ( 1 + 1 ) * ( 1 + 1 ) * ( 1 + 1 ) + ( 1 + 0 ) )] +
    $X[-1 + 0 )] +
    $X[-1 + 1 )] +
    $X[-1 + 1 )] +
    $X[1 + 1 ) * ( 1 + 1 ) * ( 1 + 1 )]

( 1 + 1 ) is 2, and ( 1 + 0 ) is 1. (It’s true. I looked it up.)

$X = "$(@{})"
$X[-2 * 2 * 2 + 1 )] +
    $X[-1] +
    $X[-2] +
    $X[-2] +
    $X[2 * 2 * 2]

A little more math, more advanced this time, gives us this.

$X = "$(@{})"
$X[-9] +
    $X[-1] +
    $X[-2] +
    $X[-2] +
    $X[8]

That’s now short enough that we can put the second expression all back on one line.

$X = "$(@{})"
$X[-9] + $X[-1] + $X[-2] + $X[-2] + $X[8]

Let’s turn our attention to what $X is.

This is the third dynamic type conversion.

At first glance, it’s just a string with random symbols. But those are double quotes.

This is an expandable string. The dollar sign and parenthesis denote a subexpression. The subexpression is just @{}. An empty hashtable. So the result of the subexpression is not a string, but PowerShell knows it’s supposed to be, so it automatically converts it.

Conversions to string are accomplished using the .ToString() method that exists on each .Net object. Some object types, like [datetime], have very useful implementations of .ToString(). Other object types, like [hashtable], have (normally) completely useless implementations of .ToString(). They convert to a string of the fully qualified name of the type of thing they are, which in this case is “System.Collections.Hashtable”.

$X = "System.Collections.Hashtable"
$X[-9] + $X[-1] + $X[-2] + $X[-2] + $X[8]

So $X is just a string. And the second line indexes into the string. Positive numbers refer to position from the start of the string, counting from 0. Negative numbers refer to position from the end of the string, counting from -1.

The second line is five characters being added together.

This is the fourth dynamic type conversion.

When you index into a string, PowerShell gives you a [char] object. But [char] objects can’t be added. When you add them anyway, PowerShell converts them to [string] objects, which can be added or concatenated.

So let’s index into $X to get the [char] objects and then convert them to strings.

'H' + 'e' + 'l' + 'l' + 'o'

Final result

And then concatenate them together to get the final result.

'Hello'

A long way to go for that, but the journey itself is often the reward.

A different path

Instead of going all the way to the end, we can go back a few steps, and take a different path to find a more concise way of expressing the original code we started with.

$X = "System.Collections.Hashtable"
$X[-9] + $X[-1] + $X[-2] + $X[-2] + $X[8]

Another way to join strings is by using the -join operator. If we put it before an array of strings, it concatenates them together. If we put it before an array of [char] objects, PowerShell converts them to [string] objects and concatenates them together.

$X = "System.Collections.Hashtable"
-join $X[-9], $X[-1], $X[-2], $X[-2], $X[8]

We can replace the array of indexed $X references with a single $X reference with an array of indexes. (Yes, I know that sentence wasn’t helpful. Just look at the next step and you’ll see what I meant.)

$X = "System.Collections.Hashtable"
-join $X[-9, -1, -2, -2, 8]

And lastly we put the value of $X in its place, to arrive at a version of the original code that is somewhat easier to understand than the original code.

-join "System.Collections.Hashtable"[-9, -1, -2, -2, 8]

The journey continues

So there you have it. An example that used four types of dynamic type conversion to do something completely useless.

But really cool.

In the next article or two in the series, I’ll give lots of more useful places dynamic type conversion can be used. These will include uses that are so familiar and mundane that you never realized there was magic happening behind the scenes, and well as a variety of tips and tricks for using it to improve your code.