Tag Archives: Get-Date

Use Foreach When it’s Really Needed

We’re not all the same, but if we were, and you were like me, you’d have a huge number of tabs open in your browser of choice. Each of them, would have some relation to PowerShell and each would be sitting by idle, and waiting to be read. No idea where it came from, but in one of them last week, I saw this:

Get-Date -Format o | foreach {$_ -replace ":", "."}

I stared at it for a moment, and thought, why is someone piping Get-Date to foreach? Get-Date only returns a single value. Why would that need to be handed off to a looping construct? The point here is, it wouldn’t need to be, even though it works. I’ve decided I should bring this up, in case someone else is potentially going to make this same mistake. Even if you’re a Systems Administrator, you’re still going to want to write efficient, and well thought out code. Here’s how I would have expected to see this written:

(Get-Date -Format o) -replace ":", "."

Again, Get-Date is only going to provide a single returned value, and therefore, we can trust that we don’t need to loop through a set of results. It’s cleaner code, it’s tighter code, and it gives the rest of us some confidence that you’ve thought things through.

One of the things I enjoying doing is testing the speed of various ways to do the same thing. Don’t think for a minute that I didn’t do that here. This next example indicates the time to run the foreach version of this Get-Date command. The second example, indicates the time to run when foreach is not used.

Milliseconds Ticks
------------ -----
           1 19094
           1 10543
           0  7044
           0  4212
           0  3944
           0  3849
           0  4133
           0  4349
           0  4099
           0  3948
Milliseconds Ticks
------------ -----
           1 12386
           0  5277
           0  4438
           0  2479
           0  2345
           0  2325
           0  4003
           0  2352
           0  2335
           0  2311

The times are close; they’re indistinguishable to us humans, but the numbers don’t lie. There’s a better way based both on time to complete, and competency. If you wanted to see it, here’s what I used to measure these two different commands. Keep these things in mind, and keep on learning!

1..10 | ForEach-Object {
    Measure-Command -Expression {
        Get-Date -Format o | foreach {$_ -replace ":", "."}
    } | Select-Object -Property Milliseconds,Ticks
}

1..10 | ForEach-Object {
    Measure-Command -Expression {
        (Get-Date -Format o) -replace ":", "."
    } | Select-Object -Property Milliseconds,Ticks
}

Bulk Remove Variables, Carefully

Ever have one of those situations where you need to remove, or perhaps reinitialize, a set of variables and have to deal with remembering them all? This doesn’t happen often, but there are times when I need to clear, or remove, variables between loop iterations. You either need to keep track of all your variable names, or — and I wouldn’t recommend this so much — do a Remove-Variable -Name *. While the help on PowerShell 5.1.14394.1000 (5.1 preview version), says that Remove-Variable doesn’t accept wildcards, it does, and I think it always has.

Let’s start with the below example. These few lines do the following: 1. Return the number of variables in the PowerShell session, 2. Add a new variable x, 3. Return the number of variables again, 4. Remove all the variables I can (some can’t be removed), 5. Return the number of variables again (again), and 6. See if I can return my variable x.

[tommymaynard@srv01 c/~]$ (Get-Variable).Count
51
[tommymaynard@srv01 c/~]$ New-Variable -Name x -Value 'string'
[tommymaynard@srv01 c/~]$ (Get-Variable).Count
52
[tommymaynard@srv01 c/~]$ Remove-Variable -Name * -ErrorAction SilentlyContinue
[tommymaynard@srv01 c/~]$ (Get-Variable).Count
29
[tommymaynard@srv01 c/~]$ Get-Variable -Name x
Get-Variable : Cannot find a variable with the name 'x'...

While we can do this to remove the variables we’ve created in the session, we end up removing variables that weren’t necessary to remove. We’re better than this.

In the next example, we’ll add a variable prefix “dog” to all of our variables. This will allow us to find all of the variables by prefix, and then delete just those. Not a bad idea really, but it wasn’t my first thought.

[tommymaynard@srv01 c/~]$ New-Variable -Name dog1 -Value 'string1'
[tommymaynard@srv01 c/~]$ New-Variable -Name dog2 -Value 'string2'
[tommymaynard@srv01 c/~]$ New-Variable -Name dog3 -Value 'string3'
[tommymaynard@srv01 c/~]$ Get-Variable -Name dog*

Name                           Value
----                           -----
dog1                           string1
dog2                           string2
dog3                           string3

[tommymaynard@srv01 c/~]$ Remove-Variable -Name dog*
[tommymaynard@srv01 c/~]$ Get-Variable -Name dog*
[tommymaynard@srv01 c/~]$ # No results, as they've been deleted.

Had I thought of the above option first, I might’ve just gone with that idea, but let me share what I was really thinking. It’s involves the description property. When we create our variables with New-Variable, we have the option to set more than just the Name and Value. Let’s take a look at the below example, and then discuss it.

[tommymaynard@srv01 c/~]$ New-Variable -Name a -Value 'stringA' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ $StartDate = Get-Date
[tommymaynard@srv01 c/~]$ New-Variable -Name b -Value 'stringB' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ New-Variable -Name c -Value 'stringC' -Description (Get-Date)
[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c

Name                           Value
----                           -----
a                              stringA
b                              stringB
c                              stringC

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Select-Object Name,Value,Description

Name Value   Description
---- -----   -----------
a    stringA 11/03/2016 20:52:41
b    stringB 11/03/2016 20:53:05
c    stringc 11/03/2016 20:53:20

Now, it’s important to remember that a variable’s description property is a string. This means that when we put the current date in the property, it was converted to a string. That means that Thursday, November 3, 2016 20:53:20 PM became this: 11/03/2016 20:53:20. What we’ll need to do is convert it back to a datetime before we compare. We do that in the below example by casting the description property as a datetime object. If it’s not clear yet, our end goal is to remove variables that were created after a specific point in time.

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {[datetime]$_.Description -gt $StartDate}

Name                           Value
----                           -----
b                              stringB
c                              stringC

We can also use the Get-Date cmdlet to do this, as well.

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {(Get-Date -Format $_.Description) -gt $StartDate}

Name                           Value
----                           -----
b                              stringB
c                              stringC

Since we can now isolate the variables we created after a specific time, we can go ahead and blow them away. Here goes:

[tommymaynard@srv01 c/~]$ Get-Variable -Name a,b,c | Where-Object {(Get-Date -Format $_.Description) -gt $StartDate} | Remove-Variable
[tommymaynard@srv01 c/~]$ Get-Variable a,b,c

Name                           Value
----                           -----
a                              stringA
Get-Variable : Cannot find a variable with the name 'b'.
At line:1 char:1
+ Get-Variable a,b,c
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (b:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Get-Variable : Cannot find a variable with the name 'c'.
At line:1 char:1
+ Get-Variable a,b,c
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (c:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Well, that’s it for today. Now you can use description property of your variables to hold the time in which they were created. This will allow you to selective remove any of them based on a start time. I like it, although the whole prefix thing might be an easier option.

Use the Date and Time in File Names

I have functions here and there, that will at times, create files to store generated information. Part of this process is naming the file. For me, I’ll often add the date and time to my file’s name, in order to know it’s creation time, at a glance.

So what does this mean? It means that I’ll often have to jump over to my ConsoleHost, or an old function, I suppose, to be certain I’m using the same date time format. Well, that may end today, right after this post is published. Now, I’ll have another place to look — my own website — to ensure my consistency when it comes to including the same naming.

You can’t use the standard output of Get-Date in a file name, and you probably wouldn’t want to anyway. This is due to the colons included in the time; they’re invalid characters for a file name.

PS > Get-Date

Friday, October 28, 2016 9:21:28 PM

But when we put the Get-Date output in a file name, it changes the output. We loose the day of the week and month as words, and instead get a date with forward slashes, such as 10/28/2016. This conversion happens when we put the cmdlet inside a string. Take a look.

PS > New-Item -Path "$(Get-Date).txt" -ItemType File
New-Item : Cannot find drive. A drive with the name '10/28/2016 09' does not exist...
PS > "$(Get-Date).txt"
10/28/2016 09:27:04.txt

The error makes sense though, as it’s parsing 10/28/2016 as a path. Not really the point here, but good to know. Either way, those slashes and colons aren’t going in a file name. It’s not permitted.

I’ve had a consistent file naming convention that includes the date and time for awhile now. What I really like about my date format is that files are automatically sorted by year, then month, then day, and then the time.

PS > Get-Date -Format 'DyyyyMMddThhmmsstt'
D20161028T093059PM

PS > New-Item -Path "Get-Date -Format 'DyyyyMMddThhmmsstt'" -ItemType File

    Directory: C:\Users\tommymaynard

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       10/28/2016   9:32 PM              0 Get-Date -Format 'DyyyyMMddThhmmsstt'

Oops, notice the problem in that second example above? I need to ensure the command inside my string is being treated as a command, and not just standard text. In the previous example, it used my actual command, as the file name. Let’s try that again.

PS > New-Item -Path "$(Get-Date -Format 'DyyyyMMddThhmmsstt').txt" -ItemType File

    Directory: C:\Users\tommymaynard

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       10/28/2016   9:34 PM              0 D20161028T093445PM.txt

In this final example, we included some text around the date, so that I can better distinguish the reason for the file, but still have the date and time included in the name. We also used the subexpression operator —  $() — to make sure my command was treated as such. Oh, did you notice the capital D and T? I used these as separators to help better display the (D)ate and (T)ime. It makes the files name easier for me to visually parse.

PS > New-Item -Path "User_Disable_Log($(Get-Date -Format 'DyyyyMMddThhmmsstt')).txt" -ItemType File

    Directory: C:\Users\tommymaynard


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       10/28/2016   9:37 PM              0 User_Disable_Log(D20161028T095316PM).txt

That’s all for now! Enjoy the weekend.

Automate App Server (Non Visual) Website Test

This is part II of this post: http://tommymaynard.com/quick-learn-automate-app-server-visual-website-test-2016. It might be beneficial to read that first.

It was minutes before my family needed to leave the house and start our Saturday, when my desire to monitor eight of my application servers hit. In a quick moment, I wrote three commands in three PowerShell consoles and left the house.

Before I show you what was in console one, I have to show and mention a couple functions on which it relied. These functions are a bit of a continuation of the link above. The Watch-MyApp function below, will visually and textually display indicators so that I know if my app servers’ websites are responding, or not. The -Visual parameter will open an instance of Internet Explorer for each server URL, so I can visually check the app servers’ websites (see the link to view this function).  Not using the -Visual parameter, which is how it’s used here, uses Invoke-WebRequest to determine a webpage’s status code. A 200 status code (OK response) means our webpage is up, and responding. If it’s not up, it’ll indicate a timeout warning. It could also produce a different status code, if the page is up, but produces something other than a 200 status code. Other more well-known status codes are the dreaded 404 (page cannot be found), or a 5xx error which indicates a problem with the web server. It may also be helpful to know that the $MyAppServers variable contains the default properties for five servers, returned from the Get-ADComputer cmdlet.

Function Watch-MyApp {
    Param (
        [switch]$Visual
    )
    
    If ($Visual) {
        $MyAppServers.Name | ForEach-Object {
            Open-InternetExplorer -Url "$_.myapp.mydomain.com"
        }
    } Else {
        $MyAppServers.Name | ForEach-Object {
            try {
                $StatusCode = (Invoke-WebRequest -Uri "http://$_.myapp.mydomain.com" -TimeoutSec 10).StatusCode
                "$StatusCode`: $_"
            } catch {
                Write-Warning -Message "$_"
            }    
        }
    }
 }

The first of my three consoles wrapped the function above, Watch-MyApp, in a never ending loop ($true is always true). Every minute it would get the date and time and write it to a log file. Immediately after that, it would run the function to get the status code of each web server, and append it to the same log file. A minute later and it does the same thing. Here’s the command and a sample from the log file it created.

Do {
    (Get-Date).ToString() | Out-File -FilePath 'C:\MyApp.txt' -Append
    Watch-MyApp | Out-File -FilePath 'C:\MyApp.txt' -Append
    Start-Sleep -Seconds 60
} While ($true)
2/21/2016 10:48:29 PM
200: appsrv01
200: appsrv02
200: appsrv03
200: appsrv04
200: appsrv05
2/21/2016 10:49:29 PM
200: appsrv01
200: appsrv02
200: appsrv03
200: appsrv04
200: appsrv05

The second console ran another endless Do-While loop. The difference, is that this loop’s job was to copy the file that was being updated by the command in the first console, to my Dropbox folder every ten minutes. Genius, right? While I could have, I opted to not have my first command write/overwrite the file directly in my Dropbox folder. I didn’t feel it was necessary to update Dropbox each minute for hours on end. When I was back home later, I changed this from ten minutes to 30 minutes, or 1800 seconds, as it was even less important to update Dropbox then.

Do {
    (Get-Date).ToString() | Out-File -FilePath 'C:\MyApp.txt' -Append
    Copy-Item -Path 'C:\MyApp.txt' -Destination 'C:\Users\tommymaynard\Dropbox\MyApp.txt' -Force
    Start-Sleep -Seconds 600
} While ($true)

What this meant is that while I was away, I was able to check Dropbox on my phone and quickly determine if there were any status codes other than a 200. Every ten minutes and there would be an update to my file that would include the status code results for each minute in the last ten.

The final console didn’t do much other than allow me to manually check the file in Dropbox, for something other than a date entry, and status code of 200. So far so good: None of the web apps returned anything more than a 200 status code. Maybe the change at the end of the day on Friday really did fixed things. Here’s the command I used to read the file and exclude lines that had a date and a 200 status code (all the lines unless there was a problem).

Get-Content -Path 'C:\MyApp.txt' | Where-Object {($_ -notlike '*2016*') -and ($_ -notlike '200*')}

But why visually scan the file myself, when PowerShell can do that too!? This third command could’ve also been an endless loop. It could’ve automated scanning the file for me, like I was doing already, and then could’ve been set up to send me an email using the Send-MailMessage cmdlet if it found something, other than the 200 status code. I could’ve avoided looking at the file in Dropbox and just watched my email. One better, and I could’ve had my command do email to text with a specially crafted email address. Here’s what I mean for US Verizon customers: http://www.verizonwireless.com/news/article/2013/06/computer-to-phone-text-messaging.html. Many days, it feels like PowerShell is only limited by what you can think do with it.

There have been times in my life where my wife will ask me, “How would people that don’t know computers, know how to do this?” I usually answer by saying, that “They wouldn’t,” or “I don’t know, I’m not that person.” The same thing applies here: “How would a Windows System Administrator know what to do if they hadn’t already learned PowerShell?” Those three commands might take someone new with PowerShell half a day to consider, and then write. Learn it while you don’t think you need it, because one day you will.

So, to recap, while I was away doing the family thing, my computer sat on the kitchen table at home, checked on eight app servers for me and updated a file in Dropbox, that I was able to check at times while we were away. PowerShell has a purpose, and without it, you’re losing yours.

Add a Dynamic Number of Asterisks

Recently, on a Windows PowerShell forum, a person wanted a script to run each time they connected to a PowerShell remote endpoint — such as a local profile does when you open the PowerShell, console host. I recommended a couple options: Create a new endpoint, and use a ScriptsToProcess .ps1 file, or modify the default, Microsoft.PowerShell endpoint, using the Set-PSSessionConfiguration cmdlet.

To use the second option I recommended, you need to add a start up script, such as the example below does. Keep in mind that you can name the .ps1 file anything you’d like. That said, I typically name these files so that I’m certain what they go with — such as, which endpoint.

PS> Set-PSSessionConfiguration -Name Microsoft.PowerShell -StartUpScript C:\Microsoft.PowerShell.StartUpScript.ps1

In order to test my updated, Microsoft.PowerShell endpoint (on my desktop computer), I decided I would add a couple informational lines of text to the script. One would indicate the “remote” computer’s name, and the other would provide the date and time. Here’s that output.

PS> Enter-PSSession -ComputerName . # This dot indicates the local computer
You are connected to TOMMYSCOMPUTER.
6/11/2015 21:17:15 PM
[localhost]: PS C:\>

As I stared at this, I decided I would prefer that the date and time was centered under the first line, with asterisks on either side, so that both lines were the same length. This means I would need to dynamically determine how many asterisks I would need on each side of my date and time, so that the asterisks, and the date and time text, would fill up as much space as the line above. It was a new PowerShell challenge, and so I ran with it.

Before we go further, let me start by showing you an image of the end results, in case the explanation wasn’t clear. Then, we’ll walk though what I did to accomplish this task.

Add-a-Dynamic-Number-of-Asterisks-2015-01

I’ve broken down my start up script into five different parts. As you’ll see, I did things very procedurally. I wasn’t worried about using a minimal amount of commands and variables. For those with more experience, you’ll easily see how things could have been much tidier. Let’s start with the first two lines.

$Computer = "You are connected to $env:COMPUTERNAME."
$Date = Get-Date -Format G

These two lines set one variable each: $Computer and $Date. Each of these values will be used in the next example.

$TopLength = $Computer.Length
$BottomLength = $Date.Length

This section, above, determines and stores the character length of the $Computer and $Date variables. We are going to need to add asterisks for the difference between these two lengths.

$DiffInLength = $TopLength - $BottomLength
$Before = [math]::Round($DiffInLength/2)
$After = $DiffInLength - $Before

This third section puts the difference between $TopLength and $BottomLength into a variable called $DiffInLength. It then creates a $Before variable to store 1/2 of $DiffInLength — it will round up, if this amount isn’t a whole number. It then creates an $After variable, which will hold the leftovers from the previous calculation.

$Before = '*' * $Before
$After = '*' * $After

Next, as seen above, we’ll overwrite our $Before and $After variables with new values. Each of those values will be a set of asterisks. How do we know how many? We multiply the asterisk symbol (a string character) by the numeric values stored in the $Before and $After variables.

Write-Host -Object $Computer -ForegroundColor Green
Write-Host -Object $Before$Date$After -ForegroundColor Green

If you’re still following along, we then write out our two lines. The first line is made up of the $Computer variable. Following that line, we write our first set of asterisks, the date, and then the second set of asterisks.

This might have been a lot to take in, so you may consider copying and pasting each line into your PowerShell host and watch as it creates the two lines for you. Regardless of the length of your computer name, your two lines will be the same length, too.

Now, that last statement isn’t always true. What if we changed $Computer to just be the computer name, such as $Computer = $env:COMPUTERNAME? I know what happens; it’s throws errors and won’t let you connect to the endpoint. This problem begins with us subtracting a larger number from a smaller number. I’ve added some logic to skip a good portion of our script, if the $TopLength is smaller than $BottomLength. I’ve include the full script below.

$Computer = "You are connected to $env:COMPUTERNAME."
$Date = Get-Date -Format G

$TopLength = $Computer.Length
$BottomLength = $Date.Length

If ($TopLength -gt $BottomLength) {
    $DiffInLength = $TopLength - $BottomLength
    $Before = [math]::Round($DiffInLength/2)
    $After = $DiffInLength - $Before
    $Before = '*' * $Before
    $After = '*' * $After
}

Write-Host -Object $Computer -ForegroundColor Green
Write-Host -Object $Before$Date$After -ForegroundColor Green

In case someone wants to a see a tidier version of this same script, then here it is. We still use the $Before and $After variables, but no others, outside of our $Computer and $Date variables.

$Computer = "You are connected to $env:COMPUTERNAME."
$Date = Get-Date -Format G

If ($Computer.Length -gt $Date.Length) {
    $Before = '*' * ([math]::Round(($Computer.Length - $Date.Length)/2))
    $After = '*' * $Before.Length
}

Write-Host -Object $Computer -ForegroundColor Green
Write-Host -Object $Before$Date$After -ForegroundColor Green

 

Get All 13 Months in a Year

I started a little, pet project recently and it’s all based around how I store my pictures and video files. I have a base directory – C:\Users\tommymaynard\Documents\Media – that contains a subdirectory for each year: 2012, 2013, etc. Inside each of those, I have 12 other directories – one for each month. These each use the naming convention 01 – January, 02 – February, etc. Inside each of those directories, I have at least two other directories: pictures and videos. Here’s an image that may help this structure make more sense.

Get All 13 Months in a Year01

I don’t always sort my photos and videos right away. Instead, I’ll often procrastinate and just drop them into a ‘Pics to Sort’ folder on my desktop – that’s literally the directory’s name. I wondered if I would procrastinate any less, if I had Windows PowerShell build out the desired directory structure for me each year. As an aside, if you consider that I always add my pictures and videos to the same I’ll-sort-you-later directory, then I could easily build a solution to do the sorting, based on file extension and creation date, from this directory into the proper subdirectories in the media directory. I’ll save this one for another day, but thanks for thinking of it.

For this Quick Learn, let’s only concern ourselves with capturing the month’s names. While I could have created a hard-coded array of the month names, I decided to see what I could get return using Get-Date – not what I wanted. I could return the current month’s name using Get-Date -Format MMMM, or the current month’s numeric representation using Get-Date | Select-Object Month, but neither of these return all of the months. The system knows the names of the months and so I opted to look for a .NET way to get them.

So, here it is, the System.Globalization.DateTimeFormatInfo .NET class. When you enter this as the value of the New-Object’s -TypeName parameter, it will return a great deal of information, to include a property called MonthNames. In the example below, you can see the code used to store the month names in a variable. You would think I would be done here, but I wasn’t.

PS C:\> $MonthNames = (New-Object System.Globalization.DateTimeFormatInfo).MonthNames
PS C:\> $MonthNames
January
February
March
April
May
June
July
August
September
October
November
December

PS C:\>

Instead of creating directories, like my project does, the example below simply echos the month’s numeric value, a space-dash-space, and then the month’s name. I’ve added a for statement example as well, just in case someone were to wonder why I didn’t include one, since I was incrementing a counter variable and knew how many looping iterations I had to do.

PS C:\> $i = 1
PS C:\> $MonthNames | foreach {Write-Output "$i - $_";$i++}
1 - January
2 - February
3 - March
4 - April
5 - May
6 - June
7 - July
8 - August
9 - September
10 - October
11 - November
12 - December
13 -
PS C:\>
PS C:\> for ($i=1; $i -le $MonthNames.Count; $i++) {echo "$i - $($MonthNames[$i-1])"}
1 - January
2 - February
3 - March
4 - April
5 - May
6 - June
7 - July
8 - August
9 - September
10 - October
11 - November
12 - December
13 -
PS C:\>

Based on these results, the MonthNames property has a thirteenth entry, and that’s a problem, since there’s no thirteenth month. I proved to myself that there was a problem a couple different ways, as seen below. Remember that the first index, or element, of an array is index zero, making January $MonthNames[0] and December $MonthNames[11].

PS C:\> $MonthNames.Count
13
PS C:\> $MonthNames[0]
January
PS C:\> $MonthNames[11]
December
PS C:\> $MonthNames[12]

PS C:\> $MonthNames[13]
PS C:\>

I needed to ensure this extra, blank month wasn’t making its way into my code. While I could have done my filtering when I was actually creating new directories, it is a better practice to only store what I need, and to only store what’s correct. As can be seen below, I only returned twelve months by adding a pipeline and some filtering.

PS C:\> $MonthNames = (New-Object System.Globalization.DateTimeFormatInfo).MonthNames | Where-Object -FilterScript {$_ -notlike ''}
PS C:\> $MonthNames
January
February
March
April
May
June
July
August
September
October
November
December
PS C:\> $MonthNames.Count
12

Since I was already here, I checked out another property. DayNames didn’t have a blank entry, and had I needed to use it, I wouldn’t have had to do any filtering on the returned results – something I incorrectly assumed about MonthNames. Hope this is helpful to someone, and thank you for reading this post.

PS C:\> $DayNames = (New-Object System.Globalization.DateTimeFormatInfo).DayNames
PS C:\> $DayNames
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
PS C:\> $DayNames.Count
7
PS C:\>

Find Time between Two Dates

This post will show two ways to determine the time span between two datetimes.

Earlier this year we performed a data migration to our data center. One of the Assistant Directors sent me an instant message first thing on a Monday morning to ask if the migration was complete. I had read that it completed earlier that morning and so I replied with the time at which it ended. The second question was how long the entire data migration took. I knew it started at 11 a.m. on Friday and that it completed at 7:21 a.m. on Monday. I could have done this in my head (11 a.m. Friday to 11 a.m. Monday equals 3 days, minus 4 hours, etc.) but I trust Windows PowerShell with dates, times, and time spans more than I do myself.

The first thing I did was assign a variable, $StateDate, with the date and time when the data migration started. After testing to see it held the proper date and time, I created a second variable, $EndDate, and assigned it the date and time when the data migration ended. I also checked that it stored the correct date and time before moving on.

PS C:\> $StartDate = Get-Date '5/9/2014 11:00:00 AM'
PS C:\> $StartDate

Friday, May 09, 2014 11:00:00 AM

PS C:\> $EndDate = Get-Date '5/12/2014 07:21:00 AM'
PS C:\> $EndDate

Monday, May 12, 2014 7:21:00 AM

I was aware of the New-TimeSpan cmdlet, which we’ll use below, but I wondered if I could simply subtract the start date (the smaller date and time) from the end date (the larger date and time). I tried and it worked! Two days, 20 hours, 21 minutes.

PS C:\> $EndDate - $StartDate

Days              : 2
Hours             : 20
Minutes           : 21
Seconds           : 0
Milliseconds      : 0
Ticks             : 2460600000000
TotalDays         : 2.84791666666667
TotalHours        : 68.35
TotalMinutes      : 4101
TotalSeconds      : 246060
TotalMilliseconds : 246060000

Here’s an example of using the New-TimeSpan cmdlet. Using this cmdlet may be easier, as to not confuse which date to subtract from which.

PS C:\> New-TimeSpan -Start $StartDate -End $EndDate

Days              : 2
Hours             : 20
Minutes           : 21
Seconds           : 0
Milliseconds      : 0
Ticks             : 2460600000000
TotalDays         : 2.84791666666667
TotalHours        : 68.35
TotalMinutes      : 4101
TotalSeconds      : 246060
TotalMilliseconds : 246060000

I didn’t want anything more returned to me other than the days, hours, and minutes. I piped my results from the New-TimeSpan cmdlet to the Select-Object cmdlet and specified the properties I wanted returned. The output wasn’t great (see the final example on this page) and so I took those results and piped them to the Format-List cmdlet.

PS C:\> New-TimeSpan -Start $StartDate -End $EndDate | Select-Object Days,Hours,Minutes | Format-List

Days    : 2
Hours   : 20
Minutes : 21

Here’s how I finished up. I created a new variable, $TimeSpan, and assigned it the results of the New-TimeSpan cmdlet piped the Select-Object cmdlet. Before going any further, I tested that the variable’s value was storing what I wanted. Once I was sure the  variable contained what I wanted, I modified it a bit and sent it to the clipboard so it could be easily pasted into my instant messenger program.

PS C:\> $TimeSpan = New-TimeSpan -Start $StartDate -End $EndDate | Select-Object Days,Hours,Minutes
PS C:\> $TimeSpan

                                   Days                                   Hours                                 Minutes
                                   ----                                   -----                                 -------
                                      2                                      20                                      21

PS C:\>"$($TimeSpan.Days) Days, $($TimeSpan.Hours) Hours, $($TimeSpan.Minutes) Minutes" | clip

about_Variables

This post is the help rewrite for about_Aliases. While the help files for Windows PowerShell are invaluable, the idea behind a rewrite is so true beginners might even better understand the help file concepts. At times, some things discussed in the Windows PowerShell help file will not be included in a help rewrite. Therefore, it is always best to read the actual help file after reading this post. (PS3.0)

A variable in Windows PowerShell is a storage container in memory that can hold a value or values. Variables can store numbers, letters, strings (a sequence of numbers, letters, and/or other characters), and the results of command that has been run in Windows PowerShell. Variables are defined by a dollar sign ($) and a string of text that follows.

PS C:\> $myVariable
PS C:\> $Process
PS C:\> $UserName
PS C:\> $a
PS C:\> $Var

Windows PowerShell has three types of variables. There are user-created variables, automatic variables, and preference variables. User-created variables are created by a user such as the variables in this example.

PS C:\> $Name = 'Macy Jones'
PS C:\> $Number = 10

Automatic variables store the state of Windows PowerShell, such as the $PSHOME variable, which stores the install location of Windows PowerShell. This type of variable cannot be changed by a user. This example shows what happens when a user tries to change the value of the $PSHOME automatic variable.

PS C:\> $PSHOME
C:\Windows\System32\WindowsPowerShell\v1.0
PS C:\> $PSHOME = 'C:\Windows'
Cannot overwrite variable PSHOME because it is read-only or constant.
At line:1 char:1
+ $PSHOME = ‘C:\Windows’
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (PSHOME:String) [], SessionStateUnauthorizedAccessException
    + FullyQualifiedErrorId : VariableNotWritable

Preference variables store a default value but can be changed. These types of variables include the $MaximumAliasCount variable that stores the maximum number of aliases Windows PowerShell will store (the default value for this variable is 4096). This example show how this variable’s value can be changed.

PS C:\> $MaximumAliasCount
4096
PS C:\> $MaximumAliasCount = 2000
PS C:\> $MaximumAliasCount
2000
PS C:\> $MaximumAliasCount = 4096

Variables are created by combining a dollar sign ($) and text string. It is beneficial to name variables in such a way that the name helps define what the variable will store. Then use the = operator to assign, or set, the variable with a value. This example shows two variables, $Name and $Number, being set to two different values. To display the value assigned to a variable, type a dollar sign and the variable name and press enter.

PS C:\> $Name = 'Macy Jones'
PS C:\> $Number = 10
PS C:\> $Series = 1,2,3
PS C:\> $Name
Macy Jones
PS C:\> $Number
10
PS C:\> $Series
1
2
3

The variable’s values can also be displayed using the Write-Output cmdlet, as well as the aliases for Write-Output, write and echo. This is used more often in scripts as opposed to the Windows PowerShell console.

PS C:\> Write-Output $Name
Macy Jones
PS C:\> write $Name
Macy Jones
PS C:\> echo $Name
Macy Jones

While the Write-Host cmdlet can also display a variable’s value, in most cases it should not be used in place of Write-Output.

PS C:\> Write-Host $Name
Macy Jones

Variable names are not case-sensitive. The case of a variable name does not matter when it is assigned or used. This example also indicates how to assign a new value to a variable that already had a value.

PS C:\> $name
Macy Jones
PS C:\> $NAME
Macy Jones
PS C:\> $NamE
Macy Jones
PS C:\> $NAME = 'Lance Andrews'
PS C:\> $name
Lance Andrews
PS C:\> $name = 'Macy Jones'
PS C:\> $NAMe
Macy Jones

Variables can hold the results of commands. The first part of this example uses the Get-Process cmdlet to immediately display the first four running processes. In the second part of the example, the first four running processes are stored in a variable and then displayed when the variable is entered.

PS C:\> Get-Process | Select-Object -First 4

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    224      19     3436        772   110    16.80   4612 ALMon
    164      14     2476       2100    44     5.53   2744 ALsvc
     77       9     1336       5288    75   140.70   4076 ApMsgFwd
     90       8     1372       5852    76   162.11   4324 ApntEx

PS C:\> $Processes = Get-Process | Select-Object -First 4
PS C:\> $Processes

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    224      19     3436        772   110    16.80   4612 ALMon
    164      14     2476       2100    44     5.53   2744 ALsvc
     77       9     1336       5288    75   140.73   4076 ApMsgFwd
     90       8     1372       5852    76   162.11   4324 ApntEx

This examples sets, or assigns, the $Date variable to the results of the Get-Date cmdlet.

PS C:\> $Date = Get-Date
PS C:\> $Date

Thursday, May 01, 2014 9:20:30 PM

The Clear-Variable cmdlet, or clv alias, in this example will remove the value that has been assigned to a variable without destroying, or removing, the variable itself. When referencing the variable, the dollar sign ($) is not used with either of these two cmdlets or with the Get-Variable cmdlet. The Get-Variable cmdlet will list all the variables in the session or list a single variable when a variable name is supplied.

PS C:\> $Name
Macy Jones
PS C:\> Clear-Variable Name
PS C:\> $Name
PS C:\> Get-Variable Name

Name                           Value
----                           -----
Name

The Remove-Variable cmdlet, or rv alias, in this example will completely remove a variable and its stored value from memory.

PS C:\> $Color = 'Green'
PS C:\> $Color
Green
PS C:\> Remove-Variable Color
PS C:\> $Color
PS C:\>
PS C:\> Get-Variable Color
Get-Variable : Cannot find a variable with the name ‘Color’.
At line:1 char:1
+ Get-Variable Color
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Color:String) [Get-Variable], ItemNotFoundException
    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

Variables can store different types of data. Normally they make their type determination based on the value(s) assigned to them. They can store integers, strings, arrays, and more. A single variable, when it is an array, can contain different types of data at the same time. The examples below use the Get-Member cmdlet to returns properties (and more) about our variable. The Select-Object cmdlet has also been used to help filter what is returned.

PS C:\> $a = 12
PS C:\> $a | Get-Member | Select-Object TypeName -Unique

TypeName
--------
System.Int32

PS C:\> $a = 'Word'
PS C:\> $a | Get-Member | Select-Object TypeName -Unique

TypeName
--------
System.String

PS C:\> $a = 12,'Word'
PS C:\> $a | Get-Member | Select-Object TypeName -Unique

TypeName
--------
System.Int32
System.String

A variable can be forced to be a certain type by casting the variable. In the first part of the example below, the variable $Number will be cast with an int type (int, as in, integer). Even though the variable is cast as an integer, it is able to handle being assign a string value of ‘12345.’ This is because the variable can change that string into a numeric value. It cannot do the same thing with the string ‘Hello.’

Further down in the example, the $Words variable has been cast as a string. When it is set to a numeric value it converts the numeric value into a string value. If the variable is used in a mathematical equation, such as addition, it does not add the two values and instead will concatenate, or join, them.

PS C:\> [int]$Number = 10
PS C:\> $Number
10
PS C:\> $Number = '12345'
PS C:\> $Number
12345
PS C:\> $Number = 'Hello'
Cannot convert value “Hello” to type “System.Int32”. Error: “Input string was not in a correct format.”
At line:1 char:1
+ $Number = ‘Hello’
+ ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException
PS C:\> [string]$Words = 'Hello'
PS C:\> $Words
Hello
PS C:\> $Words = 2
PS C:\> $Words
2
PS C:\> $Words + 10
210
PS C:\> $Number
12345
PS C:\> $Number + 10
12355

There are differences between using single quotes – which should be used as often as possible – and double quotes. Single quotes around a variable will not expand the value stored in the variable; however, using double quotes will expand the variable.

PS C:\> $Name = 'Macy Jones'
PS C:\> 'Her name is $Name'
Her name is $Name
PS C:\> "Her name is $Name"
Her name is Macy Jones

Although variable names can include spaces and special characters, it should be avoided as it can quickly lead to confusion. Using spaces and special characters requires the variable name be enclosed in curly brackets {}.

PS C:\> ${!@#$} = 'Monday'
PS C:\> ${Favorite Day} = 'Friday'
PS C:\> ${!@#$}
Monday
PS C:\> ${Favorite Day}
Friday

Windows PowerShell creates a variable drive that looks and acts a lot like a file system drive. You can access data in the variable drive the same way things are accessed in a file system. The first example uses Get-ChildItem to get the first 4 folders in C:\Windows. The second example does the same thing but instead returns the first four variables in the variable drive.

PS C:\> Get-ChildItem C:\Windows | select -First 4

    Directory: C:\Windows

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----         11/5/2013   3:31 PM            ADAM
d----         7/13/2009  10:32 PM            addins
d----         7/13/2009   8:20 PM            AppCompat
d----         4/11/2014   5:22 PM            AppPatch

PS C:\> Get-ChildItem variable:\ | select -First 4

Name                           Value
----                           -----
!@#$                           Monday
$                              4
?                              True
^                              Get-ChildItem

The only other variable cmdlet that was not discussed is the New-Variable cmdlet. This cmdlet is often not used since a variable can be created without it. The first example below shows how to return all the variable-relate cmdlets. The second example shows how to use New-Variable.

PS C:\> Get-Command *-Variable

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Clear-Variable                                     Microsoft.PowerShell.Utility
Cmdlet          Get-Variable                                       Microsoft.PowerShell.Utility
Cmdlet          New-Variable                                       Microsoft.PowerShell.Utility
Cmdlet          Remove-Variable                                    Microsoft.PowerShell.Utility
Cmdlet          Set-Variable                                       Microsoft.PowerShell.Utility

PS C:\> New-Variable -Name DaysInYear -Value 365
PS C:\> $DaysInYear
365

Bonus Information

There may come a time when two (or more) variables needs to be set to the same value. These do not need to be set individually. This first example shows how to set two variables at the same time and the second example show how to set three variables at the same time.

PS C:\> $a = $b = 'Windows PowerShell'
PS C:\> $a
Windows PowerShell
PS C:\> $b
Windows PowerShell
PS C:\> $x = $y = $z = 42
PS C:\> $x
42
PS C:\> $y
42
PS C:\> $z
42

Real World

When values of a variables are displayed in the console it will vary rarely follow the Write-Output cmdlet. This cmdlet is most often used in scripts than it is with commands written in the console.

Learn More

This information, and more, is stored in the help file about_Variables that comes with Windows PowerShell. This information can be read by typing any of the commands below. The first example will display the help file in the Windows PowerShell console, the second example will open the full help in it’s own window, the third example will send the contents of the help file to the clipboard (so it can be pasted into Word, Notepad, etc.), and the fourth example will open the help file in Notepad.

PS C:\> Get-Help about_variables
PS C:\> Get-Help about_variables -ShowWindow
PS C:\> Get-Help about_variables| clip
PS C:\> Notepad $PSHOME\en-us\about_Variables.help.txt

Change the Console Window Title – Part 1

This post will introduce the $Host automatic variable, its properties, and how we can use it to display the date in the Window PowerShell console’s window title.

The $Host variable is an automatic variable, and an automatic variable in Windows PowerShell stores “state information” about the PowerShell session. These types of variables are created and maintained by PowerShell. The example below shows the properties of the $Host automatic variable. We can return this same information by also using the Get-Host cmdlet, as seen in the second example.

PS C:\> $Host

Name             : ConsoleHost
Version          : 3.0
InstanceId       : 30d047c2-0565-4947-8f34-7ff2b42d3590
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : en-US
CurrentUICulture : en-US
PrivateData      : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
IsRunspacePushed : False
Runspace         : System.Management.Automation.Runspaces.LocalRunspace

PS C:\> Get-Host

Name             : ConsoleHost
Version          : 3.0
InstanceId       : 30d047c2-0565-4947-8f34-7ff2b42d3590
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : en-US
CurrentUICulture : en-US
PrivateData      : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
IsRunspacePushed : False
Runspace         : System.Management.Automation.Runspaces.LocalRunspace

Note: Please not use the $Host variable, or the Get-Host cmdlet, to determine the installed version of Windows PowerShell. This variable does not return the version of Windows PowerShell; it returns the version of the host. To determine the version of Windows PowerShell use the $PSVersionTable variable and the PSVersion property.

The UI property in the previous examples has several words separated by dots. This means that the UI property is actually a collection. In most cases, a collection stores a collection of properties. The example below shows how to enumerate the UI property so we can see the properties contained in this collection.

PS C:\> $Host.UI

RawUI
-----
System.Management.Automation.Internal.Host.InternalHostRawUserInterface

Wait, what? The UI collection contained RawUI – another collection. After further enumeration, this collection returns several properties. At the bottom of property list is a property called, “WindowTitle.” This property stores the value used in the window title of the PowerShell console.

PS C:\> $Host.UI.RawUI

ForegroundColor       : DarkYellow
BackgroundColor       : DarkMagenta
CursorPosition        : 0,1
WindowPosition        : 0,0
CursorSize            : 25
BufferSize            : 120,3000
WindowSize            : 120,50
MaxWindowSize         : 120,85
MaxPhysicalWindowSize : 240,85
KeyAvailable          : False
WindowTitle           : Administrator: Windows PowerShell

In the image below, the window title accurately matches what is stored in this property.

Windows PowerShell Console Window Title 05 - Part 1

The title can be changed by assigning a different value to the property $Host.UI.RawUI.WindowTitle. In this example, the window title changes instantly when we assign a new value to the property.

PS C:\> $Host.UI.RawUI.WindowTitle = 'The new and improved title!'
PS C:\>

Windows PowerShell Console Window Title 06 - Part 1

Our goal is to keep the default value, Administrator: Windows PowerShell, append a space, and then the date, inside square brackets. We want it to look like this, “Administrator: Windows PowerShell: [06/19/2014].” To reset the value to its default, close the current PowerShell console and then open a new PowerShell console. Think about that for a moment… This value is going to reset itself to the default value every time a new PowerShell session is created, such as when a new console window is opened. We will discuss this more in a moment.

In the next two examples, the value of $Host.UI.RawUI.WindowTitle is changed by concatenating the date, and the space and those brackets, to the already existing value of this property. Notice that we do this using the += assignment operator. These two examples do the exact same thing.

PS C:\> $Date = Get-Date
PS C:\> $Host.UI.RawUI.WindowTitle += " [$Date]"
PS C:\>

Windows PowerShell Console Window Title 07 - Part 1

PS C:\> $Host.UI.RawUI.WindowTitle += " [$(Get-Date)]"
PS C:\>

Windows PowerShell Console Window Title 08 - Part 1

The time indicates the time in which the PowerShell session started – pretty useless. Using the .ToShortDateString() method returns the date without the time. Both of the next two examples do the same thing.

PS C:\> $Date = (Get-Date).ToShortDateString()
PS C:\> $Host.UI.RawUI.WindowTitle += " [$Date]"
PS C:\>
PS C:\> $Host.UI.RawUI.WindowTitle += " [$((Get-Date).ToShortDateString())]"
PS C:\>

Windows PowerShell Console Window Title 09 - Part 1

As I mentioned previously, the value stored in $Host.UI.RawUI.WindowTitle returns to its default each time a new Windows PowerShell session is opened. In order for this to stick, it needs to be added to a profile. A profile runs each time you start a new PowerShell session. If you’re not already being using a profile, then it’s time to begin. Start by reading this article: http://technet.microsoft.com/en-us/library/ee692764.aspx and then follow that up by reading the help file: Get-Help about_Profiles.

Edit: You can also read the recently added Help Rewrite for about_Profiles here: http://tommymaynard.com/hr-about_profiles/

PS C:\> Get-Help about_Profiles

That is all there is to changing the Windows PowerShell console’s window title to the current date. Watch for an upcoming post that will help determine how to update the date if the Windows PowerShell session is open for more than one day.