Using Replace() to Fix Split() (and Convert-Path)

I was working on a recent project that required scripting ACLs, and so I had a Windows PowerShell console open in addition to the ISE. This allowed me to quickly check the owner of a directory (or folder). I could press the up arrow to rerun my command, and I could quickly see if the owner had changed according to my script. Let’s say my directory is called ‘TestFolder’ and is located at the root of the C:\ drive.

PS C:\> New-Item -Path TestFolder -ItemType Directory

    Directory: C:\

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        10/21/2014   9:19 PM            TestFolder

To get the ACL information for the folder, you can use the Get-Acl cmdlet, as in the example below.

PS C:\> Get-Acl -Path .\TestFolder

    Directory: C:\

Path                                    Owner                                   Access
----                                    -----                                   ------
TestFolder                              BUILTIN\Administrators                  BUILTIN\Administrators Allow  FullCo...

Being the PowerShell enthusiast that I am, I modified my command so that only the information I wanted (the path and the owner) was being returned. This is done using the Select-Object cmdlet. Unfortunately, when the command returned those two properties, the Path property was no longer what I was expecting – take a look below. While the example above only returned the name of the folder (TestFolder), I thought I would change this to show the full path (C:\TestFolder) since I was now dealing with a string that included it.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property Path,Owner

Path                                                        Owner
----                                                        -----
Microsoft.PowerShell.Core\FileSystem::C:\TestFolder         BUILTIN\Administrators

The first thing I thought to do was to split the path at the two colons (::) and grab the second element, which I thought would end up being C:\TestFolder. The split method didn’t work so well; here’s what I ended up with.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property @{N='Path';E={($PSItem.Path).Split('::')[-1]}},Owner

Path                                                        Owner
----                                                        -----
\TestFolder                                                 BUILTIN\Administrators

The reason this didn’t work is because the Split() method doesn’t split on each occurrence of two, back-to-back colons like you might expect, it splits on every colon. Since C:\ has a colon, it split there as well. FYI: The use of [-1] returns the last element in an array – good to know, I know. Here’s an example that may help better explain. In this example below, the string is split on every exclamation point (!) and every question mark (?) – not only on the combination of both (!?).

PS C:\> $String = 'Today is the 21st! That is great news, right?'
PS C:\> $String
Today is the 21st! That is great news, right?
PS C:\> $String.Split('!?')
Today is the 21st
 That is great news, right

PS C:\>

What I then decided to do to get this the way I wanted it, was to first replace the two, back-to-back colons with a single character (that was not a part of the string), and then split on that single character. It worked, and here’s what that looks like.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property @{N='Path';E={(($PSItem.Path).Replace('::','@')).Split('@')[-1]}},Owner

Path                                                        Owner
----                                                        -----
C:\TestFolder                                               BUILTIN\Administrators

PS C:\>

It was about this point, that I wanted to see if the -split operator would have handled this the same way and required the additional work that the Replace()/Split() methods did. Of course, after all I did to get this to work how I wanted, I determined I should have started with the -split operator. The -split operator isn’t looking at the characters individually, but instead of, as a whole – two, back-to-back colons is two back-to-back colons.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property @{N='Path';E={(($PSItem.Path) -split '::')[-1]}},Owner

Path                                                        Owner
----                                                        -----
C:\TestFolder                                               BUILTIN\Administrators

A note, if the @{N=…;E={…}} syntax is confusing, or new to you, then spend some time reading this: http://technet.microsoft.com/en-us/library/ff730948.aspx, and then run Get-Help about_Hash_Tables.

This happens just about every time I get to ready to publish a new post. I discovered a better way to handle the problem – much like I did when I considered the -split operator.  It turns out that I could have used a built-in cmdlet to convert the path for me. That’s right, there’s a cmdlet that would have handled everything. Using the Convert-Path cmdlet will convert something like this: Microsoft.PowerShell.Core\FileSystem::C:\TestFolder to this: C:\TestFolder. Here’s the example.

PS C:\> Get-Acl -Path .\TestFolder | Select-Object -Property @{N='Path';E={Convert-Path $PSItem.Path}},Owner

Path                                                        Owner
----                                                        -----
C:\TestFolder                                               BUILTIN\Administrators

PS C:\>

Well, that’s it for this one. As much as I might seem irritated about how I did this three different ways – from the most work to the least – I understand how important this learning process is, and that one day I will be grateful for having gone down this path… (pun intended).

Clear-Host, Without Clearing the Host

After you read this, read part 2 (and download the TMModule)

I use the Clear-Host cmdlet alias, cls, throughout the day to clear out whatever typing I have inside my Windows PowerShell console. It does its job well, but recently I’ve wanted it to work differently. I wanted it to appear that the console host has been cleared, but still allow me to scroll back up to see what was on the screen before it was cleared. I started playing around with the console class, [Console]. While not necessary, this can also be written using the namespace, System, such as [System.Console]. I like the idea of being as complete as possible and so you’ll see me use the namespace even though it’s not necessary.

Before I could write something reusable, such as a function, I had to figure out if what I wanted to accomplish, was even possible. I knew I was working with [System.Console] and so I piped that to Get-Member, but it returned the methods and properties of System.RuntimeType, seen below.

PS C:\> [System.Console] | Get-Member

    TypeName: System.RuntimeType

I struggled for a moment until I remembered an article I had read on using static classes. I found that page again, http://technet.microsoft.com/en-us/library/dd347632.aspx, and was quickly reminded that using the -Static parameter of the Get-Member cmdlet would get me the correct results.

PS C:\> [System.Console] | Get-Member -Static

    TypeName: System.Console

Running the command above produces the TypeName as shown, but it also produces all the methods and properties. I started looking over the properties and a couple about the cursor quickly caught my eye, especially the CursorTop property. After the Get-Member command from above, and based on the results of returning the CursorTop property, my cursor was positioned at line 59 inside my console, as can been seen in the example below on line 2. I cleared the screen, and beginning on line 4 below, I reran the command three more times. Each time, it gave me the location where the cursor was last positioned.

PS C:\> [System.Console]::CursorTop
59
PS C:\> cls
PS C:\> [System.Console]::CursorTop
1
PS C:\> [System.Console]::CursorTop
3
PS C:\> [System.Console]::CursorTop
5
PS C:\>

I decided I would assign the value, 0, to the CursorTop property and suddenly I was writing over the text on the top line. Take a close look at line 1 below.

PS C:\> blahblahConsole]::CursorTop
59
PS C:\> cls
PS C:\> [System.Console]::CursorTop
1
PS C:\> [System.Console]::CursorTop
3
PS C:\> [System.Console]::CursorTop
5
PS C:\> [System.Console]::CursorTop = 0

I could move my cursor, great, but this wasn’t exactly what I wanted. What I wanted was to push that scroll bar down so that anything that was already on the screen was pushed off the top of my console, and all that was left was my PowerShell prompt. I still believed there was a way to do this and so I spent a little more time looking over the properties. I found four that began with Window – WindowHeight, WindowLeft, WindowTop, and WindowWidth – and began to experiment with them. I didn’t suspect I’d be doing anything with the height and width but I thought I would check out their values anyway – 50 and 120, respectively.

PS C:\> [System.Console]::WindowHeight
50
PS C:\> [System.Console]::WindowWidth
120
PS C:\>

WindowLeft didn’t seem to be that important, because no matter how much was typed before I entered [System.Console]::WindowLeft, the property value was still set to 0. Then I entered in [System.Console]::WindowTop and it was also 0 every time. Then it dawned on me, what if I changed its value like I did with CursorTop. I tried it and my scroll bar started jumping all over. I’m getting close!

PS C:\> [System.Console]::WindowTop = 10
PS C:\> [System.Console]::WindowTop = 200
PS C:\> [System.Console]::WindowTop = 2000
PS C:\> [System.Console]::WindowTop = 0

We know the CursorTop value changes, so what would happen if we set the value of WindowTop to CursorTop? I tried it, and it worked!

PS C:\> [System.Console]::WindowTop = [System.Console]::CursorTop
PS C:\>

I thought I was done when I took another moment and scanned over the methods. I found one called SetWindowPosition. Instead of simply assigning a new value to the property WindowTop, I decided I would use the method to do the work for me. I eventually ran both of these options through the Measure-Command cmdlet and determined that there was no gain in speed by using one option over the other.

So, once I knew what to do, I opened my profile ($PROFILE) and created an empty function. For whatever reason, I called it clx thinking that this would be a good option for me. Turns out that while clx has little meaning, I was able to quickly remember it and start using it right away. Now, every time I want it to appear that I’ve cleared my host, but didn’t really, I type clx and press Enter.

Function clx {
    [System.Console]::SetWindowPosition(0,[System.Console]::CursorTop)
}

I added one additional feature to this function as is seen in the example below. This option allowed me to run the clx function and leave the last n number of rows on the screen. Try it out by ensuring your have some output in your console and then entering clx 2. This will “clear” the console screen but still allow you to view the last two rows without scrolling back up. Try it and it may make more sense.

Function clx($SaveRows) {
    If ($SaveRows) {
        [System.Console]::SetWindowPosition(0,[System.Console]::CursorTop-($SaveRows+1))
    } Else {
        [System.Console]::SetWindowPosition(0,[System.Console]::CursorTop)
   }
}

Here’s a video of the function in action. The first thing we do is return 5 processes and then 5 services. Then we use cls and notice that we cannot scroll back up to see what was cleared. This is the typical behavior. When we add the processes and services back, and then use the clx function, we can see that we have the option to scroll back up and see what was on the screen, before we “cleared” it.

Cmdlets of the Same Name (VMware & Hyper-V)

Update: This post was submitted to PowerShell.org for their PowerShell TechLetter. For the most update to date version of this post, please read it here: http://powershell.org/techletter/issues/2015-01-january.php#Article1

One of the first things I did when I moved to Windows 8.1 (and Windows PowerShell 4.0, by default), was to add the Hyper-V feature. If you didn’t already know, yes, you can run virtual machines in Windows 8.1 without the need for third-party software. I can’t be 100% certain, but I’m fairly certain I installed this feature a year ago on Windows 8, as well. In both operating systems, you will need to meet some hardware requirements to install Hyper-V, otherwise the feature will be grayed out. In addition, it may not even be listed in non-Pro and non-Enterprise versions of Windows 8.1.

Adding Hyper-V was twofold. One, I wanted to gain experience with the Hyper-V cmdlets and two, I wanted to add at least a couple virtual machines to my computer – Windows 8.1 to test PowerShell 5.0, and Windows 7 with PowerShell 3.0. After a day or so of playing with Hyper-V, it occurred to me that my future work with PowerCLI, VMware’s vSphere PowerShell PSSnapin, meant there would be name overlap between cmdlets in Hyper-V and cmdlets in VMware. This includes popular cmdlets, such as Get-VM and Get-VMHost.

In previous experience, I had learned about command precedence (Get-Help about_Command_Precedence). These are the precedence rules that are used when a command runs. If we have an alias, a function, a cmdlet, and a native Windows command, all with the same name, it will run the alias first when the name is entered into the PowerShell console. If we don’t have an alias with that name, but do have a function, a cmdlet, and native Windows command, then it will run the function. If the name is the same, but the command type is different, then they will always run in order from alias to function to cmdlet to native Windows command.

If we have two of the same command type and they have the same name, such as the cmdlet Get-VM, it will run the one that was added most recently. This is unless we provide the cmdlet a path. For instance, if the Hyper-V module was loaded and then the VMware PSSnapin was loaded, when we run Get-VM it will run the cmdlet from VMware. If we wanted to use the Get-VM cmdlet from the Hyper-V module, we would need to enter the full path that includes the module name: Hyper-V\Get-VM. Just because one cmdlet was loaded more recently, doesn’t mean the other one is gone.

In my mind, there’s a couple ways to fix the problem, or rather, make it easier to use cmdlets with the same name. The first option I tried, was to determine if the Add-PSSnapin cmdlet, that is used by VMware, had a -Prefix parameter. It didn’t, but had it, I could have added a prefix to all the VMware cmdlets with an option like the example below.

Add-PSSnapin -Name VMware.VimAutomation.Core -Prefix VMware

This doesn’t actually work. Read the post.

An option like this wouldn’t have clobbered my Hyper-V cmdlets, and therefore, Hyper-V’s Get-VM cmdlet would still work without a path, and VMware’s Get-VM cmdlet, for example, would have been Get-VMwareVM. The Import-Module cmdlet does have a -Prefix parameter so I could have changed the Hyper-V cmdlets to use a prefix, but I didn’t think the Hyper-V cmdlets should be the ones to suffer. I’m a PowerShell enthusiast, and I didn’t want to change the naming of Microsoft-built cmdlets, and therefore, learn them incorrectly.

Here’s what I did. I added a function to my profile ($PROFILE) that would allow me to choose which set of cmdlets would be loaded first, and which would be loaded second. The cmdlets I loaded second wouldn’t need the path to use them. This meant I could set the Hyper-V cmdlets to not need a path when I work with those, or I could set VMware cmdlets to not need a path when I work with those.

I started by declaring an empty function called Add-VMCs (Virtual Machine Cmdlets) that included a single parameter called Default.

Function Add-VMCs($Default) {

}

I started the function with an If statement. On line 2, below, it checks the parameter that has been provided when calling the function. If it matches the letter h or the letter v it will continue to line 3. If it does not, it will jump to line 7 and run the Else portion – displaying a message that nothing was changed and what parameters can be used. Line 3 and 4 remove the Hyper-V module and VMware PSSnapin, whether or not they are already loaded. While there could have been some logic to first check if they are loaded, I decided I was fine with hiding any errors that might occur (-ErrorAction SilentlyContinue) if I tried to unload a module or PSSnapin that wasn’t already loaded. I usually handle errors better than this, but I didn’t think it was necessary for this function.

The reason this is required is because if we try to load an already loaded module or PSSnapin, it won’t actually bother doing it, or at least that’s what I think is going on. This would prevent the function from ensuring the module and PSSnapin were loaded in my preferred order. Remember, if cmdlet names are the same, the most recently loaded cmdlet will be the one that is used.

Function Add-VMCs($Default) {
    If ($Default -eq 'h' -or $Default -eq 'v') {
        Remove-Module -Name Hyper-V -ErrorAction SilentlyContinue
        Remove-PSSnapin -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue

    } Else {
        Write-Output -Verbose "INFO: No changes made`r`nUse H to set Hyper-V as the default (Add-VMCs H) or use V to set VMware as the default (Add-VMCs V)."
    }
 }

Like we said previously, if the parameter is equal to the letter v or the letter h, it will remove the module and PSSnapin. The function continues to an If-ElseIf statement that begins on line 5. This bit of logic takes different actions depending on the value of the parameter. If the parameter is equal to the letter h, it will load the VMware PSSnapin and then import the Hyper-V module. This means that Get-VM would be the cmdlet associated with Hyper-V. If it is not equal to the letter h, and instead it is equal to the letter v, then it will load the Hyper-V module and then the VMware PSSnapin. This means that Get-VM would be the cmdlet associated with VMware.

Function Add-VMCs($Default) {
    If ($Default -eq 'h' -or $Default -eq 'v') {
        Remove-Module -Name Hyper-V -ErrorAction SilentlyContinue
        Remove-PSSnapin -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue
        If ($Default -eq 'h') {
            Add-PSSnapin -Name VMware.VimAutomation.Core
            Import-Module -Name Hyper-V
        } ElseIf ($Default -eq 'v') {
            Import-Module -Name Hyper-V
            Add-PSSnapin -Name VMware.VimAutomation.Core
        }
    } Else {
        Write-Output -Verbose "INFO: No changes made`r`nUse H to set Hyper-V as the default (Add-VMCs H) or use V to set VMware as the default (Add-VMCs V)."
    }
}

Here’s the function in action. At first I set the Hyper-V cmdlets to be the default on line 1. I then verify that my cmdlet is from the correct module by using the Get-Command cmdlet. Once that’s been verified, I run the Get-VM cmdlet on line 8. Then, I do it all again after I change the default to VMware. Note: If you’ve ever used VMware’s PowerCLI then you know I had to use the Connect-VIServer cmdlet to connect to a vCenter system before it would allow me to run the VMWare Get-VM cmdlet.

PS C:\> Add-VMCs -Default H
PS C:\> Get-Command Get-VM

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Get-VM                                             Hyper-V

PS C:\> Get-VM -Name Win8.1*

Name        State   CPUUsage(%) MemoryAssigned(M) Uptime   Status
----        -----   ----------- ----------------- ------   ------
Win8.1PS5.0 Running 23          1536              00:26:20 Operating normally

PS C:\> Add-VMCs V
PS C:\> Get-Command Get-VM

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Get-VM                                             VMware.VimAutomation.Core

PS C:\> Get-VM -Name Windows*

Name                 PowerState Num CPUs MemoryGB
----                 ---------- -------- --------
Windows 2003 R2 S... PoweredOff 1        1.000

Linked from here:
http://blogs.technet.com/b/heyscriptingguy/archive/2014/09/04/powertip-use-complete-name-for-powershell-cmdlet.aspx

Implicit Remoting and the Exchange Cmdlets

I work in an environment where we administer Microsoft Exchange 2010 and I’m proud to say that I don’t have the Exchange Management Tools installed on my laptop. Now, that doesn’t mean I don’t sometimes RDP into an Exchange box, but it does force me to forgo that and use Windows PowerShell when I want to do something quicker than to RDP, log on, and open the EMC. One of the first things I added to my $PROFILE, after writing it for use in a script, was a function that would establish a PSSession to one of the Exchange servers. This allows me to run Exchange-specific cmdlets without leaving my laptop and without having the Exchange Management Tools installed.

The first thing I started with was an empty function that I called New-ExchangeSession.

Function New-ExchangeSession {

}

I had a few requirements for this project. One, I didn’t want to rely on connecting to the same Exchange server each time I created a PSSession. If for some reason it wasn’t available, I would have a problem connecting… in addition to a non-responsive Exchange server. Two, I didn’t want to hard code my Exchange server list in either my script (bad!) or in an external file (not as bad, but not great). What I did was make use of the Get-ADGroupMember and the Get-Random cmdlets to return the names of the Exchange Servers from an Active Directory group in which they were all members, and then randomly select one.

I did this as part of Do-Until loop. In line 3, the first thing it does is acquire the members of the group, ExchangeServers, using the Get-ADGroupMember cmdlet included in the Active Directory module. If you’re running PowerShell 3.0 or greater it will load this module automatically. If you’re not, then you’ll have to add it yourself using the Import-Module cmdlet before beginning the Do-Until loop.

Using dotted notation (.Name) we return only the Name property of the group members. Once collected, the Get-Random cmdlet is used to randomly select one of the names and then assign it to the $ExchServ variable.

Function New-ExchangeSession {
    Do {
        $ExchServ = Get-Random (Get-ADGroupMember -Identity 'ExchangeServers').Name
    }
    Until (Test-Connection -ComputerName $ExchServ -Count 1 -Quiet)
}

If you’re using something older than 3.0, and you really shouldn’t be, you’ll find the dotted notation version just doesn’t work as expected. In that case, you will have to handle this in a more procedural way as seen in the example below. Regardless of which way you get a server name and assign it to the $ExchServ variable, it must reply to a ping, by use of the Test-Connection cmdlet in line 5 above, and line 7 below. This is the conditional check of Do-Until loop. If Test-Connection returns True, it will break out of the Do-Until. If it returns False, it will randomly select another server and try again.

Function New-ExchangeSession {
    Do {
        $ExchServ = Get-ADGroupMember -Identity 'ExchangeServers'
        $ExchServ = Get-Random $ExchServ
        $ExchServ = $ExchServ.Name
    }
    Until (Test-Connection -ComputerName $ExchServ -Count 1 -Quiet)
}

We start to create the PSSession once we have an Exchange server name chosen and verified it is reachable across the network. This is done by creating a variable, $Session on line 7 below, that will store the session information. We then use that session information as part of our Import-PSSession cmdlet that brings the Exchange cmdlets down to our local computer. The final, informational message on line 9 simply indicates the Exchange server in which we’ve connected.

Function New-ExchangeSession {
    Do {
        $ExchServ = Get-Random (Get-ADGroupMember -Identity 'ExchangeServers').Name
    }
    Until (Test-Connection -ComputerName $ExchServ -Count 1 -Quiet)

    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$ExchServ.mydomain.com/powershell" -Authentication Kerberos
    Import-PSSession -Session $Session -CommandName * -FormatTypeName * | Out-Null
    Write-Output "Connected to $ExchServ"
}

You will often, if not always, receive a warning about some of the imported commands may have unapproved verbs. To view your active session, use the Get-PSSession cmdlet, and to close the session, use the Remove-PSSession -Id  #, where # equals the Id number returned from the Get-PSSession cmdlet.

Nine Essential IT Job Skills for 2015

You probably haven’t heard my story, but when I was first introduced to Monad (Windows PowerShell’s original name), I was anything but pleased. I was happy automating in VBScript; I didn’t need (or want) something different. Now, I won’t hardly go near VBScript and if I do, I better be getting paid well to do so.

I read an article a few years ago that offered 10 skills for a successful career. I don’t remember 1 through 9, but I do remember number 10. It was lean PowerShell. A year late and I read practically the same article, however, PowerShell was number 1. It was that day that I decided I would start, and complete, my next automation project in something other than VBScript. Without that second article, I would have likely continued to delay learning PowerShell, and then possibly missed out on some of the success in my career. I know for a fact that having PowerShell experience was a part of getting my current job – a job I didn’t have when I read that article.

If you haven’t started learning PowerShell, then perhaps this newest article will be what you need to start your learning. This list includes 9 IT job skills to be successful in 2015 and beyond. Guess where PowerShell is this time, too: It’s number 1.

http://www.petri.com/9-essential-it-job-skills-for-2015.htm

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

Formatting Decimal and Whole Numbers

This post will show how to use some simple logic and formatting to display only two decimal places with decimal numbers, and no decimal places with whole numbers.

I recently updated a game I wrote in Windows PowerShell called, “The 1 to 100 Game.” In this game, a person tries to guess a number between 1 and 100 that has been chosen by the computer (or rather, the advanced function). The computer will offer hints, as to whether the number it has chosen, is higher or lower than what was last guessed by the person playing the game. This person continues to guess until they correctly match the number. You can find the advanced function on the Microsoft TechNet Gallery here: http://gallery.technet.microsoft.com/The-1-to-100-Game-5288e279. While it’s not my most popular advanced function, I enjoyed the project, and especially when I shared it with my son as the 1 to 100 Game is one of the many games we play during our longer car rides. Oh, and if you’re looking for an example of nested do-while language constructs, then look no further – it’s up to three now.

During the rewrite to version 2.0, I decided to add something new. The game records the number of attempts it takes to guess the correct number, and how many games you’ve played. With that type of information, it can easily calculate the average number of attempts per game. This won’t always be a whole number and so I needed a way to only use two decimal places when decimal numbers were returned, and to use no decimal places when whole numbers were returned.

We can format our decimal numbers to only use two decimal places by using the -f Format operator. In the example below, we take a number that has four decimal places and specify that it is formatted to only use two decimal places.

PS C:\> $x = 100.9851
PS C:\> $x
100.9851
PS C:\> '{0:N2}' -f $x
100.99 #We want this, and notice the rounding!

In the next example, we have a whole number. If we use the -f Format operator on this number it will also show two decimal places. This isn’t what we want – we want to leave whole numbers the way they are and not add any decimal places.

PS C:\> $x = 150
PS C:\> $x
150
PS C:\> '{0:N2}' -f $x
150.00 #We don't want this!

Formatting decimal numbers, and leaving whole numbers alone, is going to require some conditional logic (If-Else statement) along with modulus division. Modulus division returns the remainder of a division operation. Here are some examples using the division operator (/) and the modulus operator (%).

PS C:\> 10 / 2
5
PS C:\> 10 % 2
0
PS C:\> 10 / 3
3.33333333333333
PS C:\> 10 % 3
1
PS C:\> 10 / 7
1.42857142857143
PS C:\> 10 % 7
3

When zero is returned from a modulus division operation, it means that the division operation did not result in a remainder. This is a great way to handle whether or not a number should or should not be formatted. In the example below, the modulus division operation on line 3 returns zero. Since zero is equal (-eq) to zero, then $a is divided by $b on line 4.

$a = 10
$b = 2
If ($a % $b -eq 0) {
    $a / $b
} Else {
    '{0:N2}' -f ($a / $b)
}
5

In this example, the modulus division operation does not return zero. Therefore, the else statement is called on line 5. On line 6, $a is divided by $b and then formatted to two decimal places. The parenthesis around ($a / $b)  indicates to PowerShell to divide first, and then do the formatting.

$a = 10
$b = 3
If ($a % $b -eq 0) {
    $a / $b
} Else {
    '{0:N2}' -f ($a / $b)
}
3.33

PowerShell iPhone Wallpaper

Here are two wallpaper options for Windows PowerShell enthusiasts that use an iPhone. These were made for the iPhone 5, 5c, and 5s models. Be sure to click the image to open the full size version (640 x 1136) before saving it.

iPhonePSWall01

iPhonePSWall02

If you don’t want an image behind the apps and folders on your home screens, then use this wallpaper that doesn’t have a PowerShell logo but uses the same color.

iPhonePSWallBlank

 

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 a 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 shows 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 a 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 return 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 an addition equation, 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-related 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 need 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 shows 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 variable are displayed in the console, it will very 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, are 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 its own window, and 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

about_Aliases

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)

An Alias in Windows PowerShell is a simplified, or quicker, way to type a cmdlet using an alternate name. Get-Alias (or the alias for Get-Alias, gal) will display a list of all of the aliases that the Windows PowerShell session knows about. This includes both built-in aliases and any additional aliases created or imported. The first two examples below, indicate two ways to accomplish the same thing–listing all the aliases. These examples only show the first four results.

PS C:\> Get-Alias

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           % -> ForEach-Object
Alias           ? -> Where-Object
Alias           ac -> Add-Content
Alias           asnp -> Add-PSSnapin

This example uses the alias for the Get-Alias cmdlet, gal.

PS C:\> gal

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           % -> ForEach-Object
Alias           ? -> Where-Object
Alias           ac -> Add-Content
Alias           asnp -> Add-PSSnapin

To find the cmdlet associated with a single alias, the alias needs to be provided, as the value for the -Name parameter, to the Get-Alias cmdlet.

PS C:\> gal -Name gc

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           gc -> Get-Content

The name parameter (-Name) is not required to use it. This means that if there is something after the Get-Alias cmdlet, such as gc in this example, then it will default to using the -Name parameter.

PS C:\> gal gc

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           gc -> Get-Content

Windows PowerShell will error if the -Name parameter is supplied with a cmdlet name or another value that is not an alias.

PS C:\> gal Get-Content

gal : This command cannot find a matching alias because an alias with the name ‘Get-Content’ does not exist.
At line:1 char:1
+ gal Get-Content
+ ~~~~~~~~~~~~~~~
+ CategoryInfo          : ObjectNotFound: (Get-Content:String) [Get-Alias], ItemNotFoundException
+ FullyQualifiedErrorId : ItemNotFoundException,Microsoft.PowerShell.Commands.GetAliasCommand

In order to get an alias (or aliases, if there is more than one) for a cmdlet, the -Definition parameter must be used.

PS C:\> gal -Definition Get-Content

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           cat -> Get-Content
Alias           gc -> Get-Content
Alias           type -> Get-Content

The Get-Service cmdlet returns the computer’s services, the Get-Process cmdlet returns the processes running on the computer, and the Get-ChildItem cmdlet returns the directories and/or files from the root of a drive or from a folder. Here is how a user can get the aliases for multiple cmdlets at the same time.

PS C:\> gal -Definition Get-Service,Get-Process,Get-ChildItem

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Alias           gsv -> Get-Service
Alias           gps -> Get-Process
Alias           ps -> Get-Process
Alias           dir -> Get-ChildItem
Alias           gci -> Get-ChildItem
Alias           ls -> Get-ChildItem

There are a few other cmdlets that allow a user to work with aliases. By using the Get-Command cmdlet (or its alias–if it has one), additional cmdlets can be returned that all end with -Alias.

PS C:\> Get-Command *-Alias

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Cmdlet          Export-Alias                                       Microsoft.PowerShell.Utility
Cmdlet          Get-Alias                                          Microsoft.PowerShell.Utility
Cmdlet          Import-Alias                                       Microsoft.PowerShell.Utility
Cmdlet          New-Alias                                          Microsoft.PowerShell.Utility
Cmdlet          Set-Alias                                          Microsoft.PowerShell.Utility

Export-Alias: Exports information about currently defined aliases to a file.

PS C:\> Export-Alias -Path 'C:\aliases.txt'

Import-Alias: Imports an alias, or aliases, from a file.

PS C:\> Import-Alias -Path 'C:\ImportedAliases.txt'

Trying to import aliases that already exist will cause an error for every alias Windows PowerShell tries to import (that already exists).

PS C:\> Export-Alias -Path 'C:\aliases.txt'
PS C:\> Import-Alias -Path 'C:\aliases.txt'
Import-Alias : The alias is not allowed, because an alias with the name ‘ac’ already exists.
At line:1 char:1
+ Import-Alias -Path ‘C:\aliases.txt’
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceExists: (ac:String) [Import-Alias], SessionStateException
    + FullyQualifiedErrorId : AliasAlreadyExists,Microsoft.PowerShell.Commands.ImportAliasCommand

New-Alias: Creates a new alias.
Set-Alias: Changes an existing alias, or creates an alias if it does not already exist.

PS C:\> New-Alias -Name MyAlias -Value Get-Process
PS C:\> MyAlias | select -First 4

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    224      19     3440        772   110    16.50   4612 ALMon
    164      14     2476       2108    44     5.36   2744 ALsvc
     77       9     1336       5288    75   137.55   4076 ApMsgFwd
     90       8     1372       5788    76   162.11   4324 ApntEx

PS C:\> Set-Alias -Name MyAlias -Value Get-Service
PS C:\> MyAlias | select -First 4

Status   Name               DisplayName
------   ----               -----------
Running  AdobeARMservice    Adobe Acrobat Update Service
Stopped  AdobeFlashPlaye... Adobe Flash Player Update Service
Stopped  AeLookupSvc        Application Experience
Stopped  ALG                Application Layer Gateway Service

Bonus Information

Use the Measure-Object cmdlet, or the count property, to find out how many aliases Windows PowerShell knows about.

PS C:\> Get-Alias | Measure-Object

Count    : 182
Average  :
Sum      :
Maximum  :
Minimum  :
Property :

PS C:\> gal | measure

Count    : 182
Average  :
Sum      :
Maximum  :
Minimum  :
Property :

PS C:\> (gal | measure).count
182
PS C:\> (gal).count
182

Real World

While aliases are helpful in the console, the belief is that they should not be used in a script file (.ps1 file). Using full cmdlet names in a script is preferred for script readability. There are plenty of people writing Windows PowerShell who adhere to this best practice even while using aliases for the object cmdlets (select for Select-Object, where for Where-Object, etc.).

Learn More

This information, and more, are stored in the help file about_Aliases 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 its own window, and 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_aliases
PS C:\> Get-Help about_aliases -ShowWindow
PS C:\> Get-Help about_aliases | clip
PS C:\> Notepad C:\Windows\System32\WindowsPowerShell\v1.0\en-US\about_Aliases.help.txt

There is a built-in, automatic variable $PSHOMEthat stores the installation path of Windows PowerShell. This means that the third example above could have been partially written using that variable.

PS C:\> Notepad $PSHOME\en-us\about_Aliases.help.txt