Contact

Subscribe via Email

Subscribe via RSS

Categories

Creative Commons Attribution 4.0 International License
© Rakhesh Sasidharan

Taking PowerShell on a date

I needed to filter the event logs from a remote computer for power off events. The user was powering it off himself every now and then, but wouldn’t believe me when I said so. No problem, thanks to the event logs all his shutdown requests are logged so it’s only a matter of culling it out.

I could have user Event Viewer but PowerShell is way easier. I only need to check the System event logs for any events with ID 1074. Further, for every shutdown, events with this ID are logged twice – one starting with the line “The process C:Windowssystem32winlogon.exe …” the other starting with the line “The process Explorer.EXE has initiated the power off …” – so I’d like to filter the output to just include one of these lines. Finally, I’d also like the day of the week to be shown.

So here’s what I did:

So far so good. I use Get-WinEvent to return the event logs I want, use the Message property of these returned objects to filter logs containing just the text we want. And finally I use the TimeCreated property of these objects to display the time and day. This TimeCreated property is the subject of this post so let’s focus on that further.

First, how do we get the members and properties of the objects returned by Get-WinEvent? Here’s how:

Notice the TimeCreated property.

The TimeCreated property is an object of class DateTime. To get the day of the date instance, use the TimeCreated.DayofWeek property. Which is what I extract via the Format-Table expression in my initial code:

This post from the “Hey, Scripting Guy! Blog” is a good read on Get-WinEvent and the DateTime class.

Focussing on the DateTime class, this is the type of the object returned by the get-date cmdlet too. Although not obvious from the syntax of the get-date cmdlet, you can use it to return a DateTime object instance of the date you specify as string. Like thus:

What’s more, you can even format the string:

The -Format switch takes a format specifier. As seen above “dd” stands for the date, “ddd” stands for the day in short-hand, “dddd” stands for the date in longhand, and so on.

You can also typecast a string to the DateTime class:

The typecast technique has a limitation though in that it expects the input to be in the format of US, i.e. “MM/dd/YYYY”. The DateTime class has a static member called Parse(), however, that is smart enough the use the country/ culture of the computer.

How do we parse the date if it’s in a different format though? Both the get-date cmdlet and the [datetime]::Parse() method fail if the input string does not match the date format of the computer:

It is possible to specify the date format of the input string. This only works with the Parse() method though. Like thus (thanks to this blog post):

The Globalization.cultureinfo class has a GetCultureInfo() method, so what we are doing above is we create a variable that is an instance of the “en-US” culture and then pass this variable to the Parse() method so it knows which format to parse the input in. Of course we could have also specified the whole thing on one line:

Let’s examine the Globalization.cultureinfo class. First, let’s examine its static members and properties:

The GetCulture() method can be used to list all available cultures:

To get details about a specific culture use the GetCultureInfo() method:

The LCID corresponds to the Locale ID assigned by Microsoft. To see all the properties use the get-member cmdlet or format-list to see the values:

On the topic of dates, the DateTimeFormat property seems interesting:

Notice the ShortDatePattern above. This is exactly what the -Format switch of Get-Date cmdlet expects, so one can use this to format the output of Get-Date in a different locale. Like thus:

So now we have seen to use the [datetime]::Parse() method to parse an input string into a DateTime object while specifying the country format of the input string. We have also seen how to output the result of get-date in the format of a different country. Both methods use the [Globalization.cultureinfo] class.

Lastly, there is an easy way to parse any string – irrespective of whether it matches the format of a country or not – into a DateTime object via the ParseExact() method. This is worth mentioning as without such a method one would have to resort to regular expressions and such!

In the example below we match an input string with time “02:50PM” by specifying the format as “hh:mmtt” to the ParseExact() method.

I am not sure what the third parameter of this method is supposed to be. Most examples put it as $null. It seems to take a “culture” as input, but I am not sure how it affects the output. Instead of $null the following too seem to work: $(get-culture), [Globalization.Cultureinfo]::GetCultureInfo("en-US") (“en-GB” too works), and [Globalization.Cultureinfo]::InvariantCulture.

The format can contain other text too. Escape the characters of such text with a slash or put the text within quotes ':

And just to show what happens if the format does not match:

Thanks to these two posts for showing me ParseExact().

Format-Table FormatString

In the previous post I wanted to convert some output to percentage figures and resorted to type casting to get it done. Turns out I don’t need to do that. Was reading more about the Format-Table cmdlet and saw that the hash table that supplies calculated properties also supports a key called FormatString which let’s one specify the format of the output.

For a quick overview of FormatString read this MSDN page. You can see various format specifier strings mentioned there. These let you define the format of the output.

Say I want to see the current time and output the remaining minutes to the hour as a percentage.

I can get the current minute thus:

I can use an Expression block to show the balance minutes:

And if I want to format the output such that it’s shown as a percentage (the value multiplied by 100 with a % sign suffixed) I can use FormatString with the “P” format specifier:

This gives the output as a percentage figure with two decimal places (the default). I can modify that by suffixing a number to the “P” specifying the desired number of decimal places. Like for instance:

If I want to round, I can use the “r” specifier. I can do decimal, scientific, fixed point, and so on.

Returning to the code of the previous post, here’s a revised version:

There’s two changes: (1) I don’t type cast the Capacity to [uint32] any more to round it, rather I use the “F0” format specifier which stands for a fixed-point number with 0 decimal digits (i.e. a number like 42.9517669677734 gets converted to 43); (2) I don’t type cast for the free space to percentage either, I just use the “P1” format specifier which stands for a percentage figure with 1 decimal digit (i.e. a number like 48.6%).

Neat!

Get a list of partitions and free space

The Win32_Volume class can be used to list partitions. The default output is very verbose but properties of interest to me are DriveLetter, Label, Capacity, and FreeSpace.

Ok, so let’s tidy this up a bit.

First, let’s get rid of any empty entries like the “System Reserved” partition.

Then let’s convert the capacity figure to GB and express free space as a percentage. This is easily done using Format-Table (I love playing with Format-Table to customize output! I find it tremendously exciting to be able to just format the output using an Expression script block).

Next let’s convert the capacity and free space figures to [uint32] so they get rounded as integers.

And lastly let’s include the label along with the drive letter.

Another useful property from Win32_Volume is DriveType.

This can be used to limit the output to only fixed disks, for instance. The numbers are constants that define the various types of drives (starting from the number 0) so it’s possible to filter by DriveType to limit to fixed disks only.

Just for kicks I’d like to put a % sign after each of the free space figures.

This one’s worth explaining a bit. The change that I made is in the Expression block for free space.

What I did is that I put [uint32]($_.FreeSpace/$_.Capacity*100) – the previous Expression block – within the $() operator so it is evaluated as a sub-expression. In turn I put the $() block within double quotes to type cast the number into a string. And finally I suffix the $() within double quotes with the “%” character so it’s appended to the string.

It’s important to use the $() operator here. Instead, if I had only grouped the code within brackets () what happens is that the expression $_.FreeSpace/$_.Capacity*100 gets evaluated as expected, but the result is not type cast to an integer as [uint32] is treated like a string rather than an operator due to the double quotes casting it that way. So the $() operator is required.

Since the number is a string it gets left-aligned. But that can easily be aligned to the right with the Align key:

See the next post for a different way of doing this.

Obfuscate email addresses with PowerShell

Came across the following piece of code in someone’s signature today.

It’s a geeky way of obfuscating your email address. If you copy paste that bit of code into a PowerShell window and press ENTER, you get an email address.

I’d like to make something similar. Especially since it just coincides with what I was playing around with today morning. But before that I’d also like to understand what’s going on above. So let’s break this one-liner and peek behind the curtain.

Understanding

First things first: if I remove the [string] type cast and run the code I get a bunch of characters – one on each line. So what the type cast does is convert these characters to a one-liner string.

Next, I piped the email address that was output to the Measure-Object cmdlet with the -Character switch. That gave me the number of characters in the address: 34. This coincides with the next bit of the code – 0..33 – which is shorthand for generating the numbers 0 to 33 (34 numbers). So looks like that bit is responsible for driving something to generate each character of the address.

Great. What’s happening next?

The code generates 34 numbers. For each of these numbers it does something.

First a sub-string is extracted from that long series of numbers. This long series of numbers is stored as a string (hence the double-quotes). From this a sub-string of 2 characters is extracted. The starting point of extraction jumps forward by 2 each time (position 0, then 2, then 4, 6, 10, and so on up to 66) – breaking the long string into a series of 2 digit numbers.

Here’s the relevant code:

This generates numbers 68,65,52, … 70.

To each of these the code adds the number 46. So the numbers actually output are 114,111,98, … 116. Each of these is then converted to a character – which is what the [char][int] bit does. And the rest we already know – these characters are converted to one long string showing the email address. (There’s one gotcha though, in that the string has the characters separated by a space. Which is why we have a -replace " " tacked on the end – this simply replaces are spaces with nothing, combining all the characters into one word).

Genius! (At least for a PowerShell child like me).

Creating

Okay. So how do I create something similar?

First things first: if I were to just convert an email address to ASCII/ UTF-8 codes what do I get?

I could join these numbers, with dots perhaps, to form a long string.

If I wanted to get my address from such a string I’d do something like this:

That outputs the numbers, I can just convert them back to an address.

Great. So that’s something I could paste in a signature somewhere and anyone running that code will get an email address.

I got side tracked though. What would I do to get something similar to the code the user created? Backtracking:

I have a series of numbers. They are of varying length so I can’t just club them together and hope to extract. I need to get them all to a uniform length, so an easy idea would be to just subtract them all by some number to make them 2 digits each. The number has to be larger than 46 – as that’s the smallest number – so why not just use 46 as the number to subtract from all?

So now all I need to do to reverse the process is (a) split the long number into chunks of two, (b) add 46 to each, (c) convert to a char, and (d) convert the array of characters into a string.

It worked! Sort of. Something messed up though. And the email address is mostly generated but for the last 4 characters. It looks like the string is falling short for the Substring method.

A few minutes of head scratching later, I realize the problem. When I converted all the numbers to two digits, one of them was one digit – the number 46 that got subtracted by itself. This was resulting in a string that fell short. Not to worry, all I had to do was pad a 0 to that number and then it will work.

Automating

Can I automate this process? One where any string I give is converted to code that will generate the string when run.

A proper generic code would take a bit more effort and time, but since I am mainly concerned with email addresses I can take some short cuts. If I were going to build a generic code I’d (1) need a way to find the smallest ASCII/ UTF-8 code in the set entered, (2) while subtracting that number from every number I’d have to ensure any single digit number is accordingly padded, and (3) I’d also have to be concerned with any text that may have an ASCII/ UTF-8 code which won’t reduce to two digits when I do the subtraction above.

First thing to notice is that the number that we are subtracting – in my example above, as well as the code I started off with – is 46. Which turns out to be the dot in the email address. Next thing to notice is that letters A-Z and a-z have codes 65-90 and 97-122. Similarly the numbers 0-9 have codes 48-57. So by limiting myself to this set of characters I can be sure that the lowest number will always be 46, and that I can subtract it from any of the expected characters and expect a single or double digit result.

But why not got a teeny weeny step further. Checking WikiPedia for the syntax of valid email address I see that the lowest ASCII/ UTF-8 code for an allowed character is 32 (the character being “) and the highest allowed ASCII/ UTF-8 code for an allowed character is 126 (the character being ~). So the code I generate will be similar to the code I started this post with, except that I’ll be adding 32 instead of 46. And so the code I create to generate this code will output 32.

So without much ado here’s the code:

Of course, replace “abc@example.com” with the email address you’d like to obfuscate.

For the curious, here’s an explanation of my code (the bit after the first pipeline).

Line 1 outputs the part of the final code that starts with a [string] and ends at the beginning of the series of digits (just before the left bracket).

Line 3 outputs the part of the final code that starts where the digits end (just after the right bracket) and goes all the way up to -replace " ".

These two lines are pretty straight-forward. Only point to note is I escape the $ symbols with a backtick (`) so they don’t get interpreted as variables. And I get the length of the input text via $_.Length (this is why I put the whole block within a ForEach-Object loop as that’s the only way to make $_ variable available). Also, I put the expression as $($_.Length-1) because if I don’t do that PowerShell expands $_ into a string and then tacks “.Length-1” to it as another string.

Line 2 is a variant of what I created earlier. It converts the input string into an array of characters, cast into an array of integers, and pipes these into another ForEach-Object loop that subtracts the number 32 from each and if the result is less than 9 (i.e. single digit) pads a “0” before outputting the number.

And that’s it!

Getting the ASCII/ UTF-8 value of a string

To display the character associated with an ASCII/ UTF-8 code do the following:

Thanks to this PowerTip.

Now how about the reverse? Can I get the ASCII/ UTF-8 code of a character.

No luck. But I think am the right track.

The error InvalidCastFromStringToInteger is what tells me PowerShell is trying to cast from a [string] to [int] – which is not what I want and will obviously fail. I want to cast from a [char] to [int] so let’s be explicit about that.

Good, that works!

Now how about getting the ASCII/ UTF-8 of a string. Can I do that?

As expected, you can’t just pass two characters and hope it works!

What I need is an array of [char] elements. Which I can then type cast to an array of [int] elements.

First let’s look whether there’s any method available to convert a string to an array of characters?

Looks like there is. Does the following work?

No, but that gives me a hint on the solution. The output of the ToCharArray() method is of the data type System.Char[] whereas [char] is shorthand for the System.Char data type.

So maybe [char[]] is what I need? Does such a data type exist?

Sure enough it does!

So let’s try the following:

I don’t need the ToCharArray() method either as if I just type cast a string to an array of characters the method is invoked implicitly. Sweet!

Armed with this info I try type casting the string to an array of integers to get their ASCII/ UTF-8 values:

Nice!

Can I make this better? As in, say I had a longish string; currently the above snippet just gives a bunch of codes and that’s not very helpful if I want to see the code of a particular letter. Can I get the output such that it shows each character followed by it’s ASCII/ UTF-8 code? Something like this:

D’oh! Doesn’t help. But I am on the right track, and I know what to do. You see, within double quotes the [int] is not evaluated (thanks to this Hey, Scripting Guy! post) and so I have to force evaluation through any one of the methods mentioned in that post. I prefer the VBScript approach, so here goes:

Bingo!