Keep PowerShell Cmdlets Powerful

I’ve seen what I’m about to show you done repeatedly in Windows PowerShell. With that thought in my mind, I’ve decided it’s time to officially write about it. The first time I noticed someone doing it, well, it was me. I did, however, write a correction to my team so they knew I was aware of my error, and as a hope they wouldn’t repeat it.

Those doing this may be new to PowerShell, but if you aren’t careful and don’t fully understand the capabilities of the cmdlets you use, then it’s possible you might make this mistake, too. What am I talking about? I’m talking about Invoke-Command and an improper way that it’s sometimes used.

Invoke-Command‘s purpose is to run a command, or commands, on remote computers and return the results back to the local computer. That said, it can be used on the local computer, as well; however, this is usually only necessary to check if PowerShell Remoting is working on the local computer. While I don’t normally do this in my examples, I’m piping my result of this example to Format-Table -AutoSize once they’re returned from the remote computer. This is so it’ll display better on this webpage.

Invoke-Command -Computer DC01 -ScriptBlock {Get-PSDrive -PSProvider FileSystem} | Format-Table -AutoSize
Name Used (GB) Free (GB) Provider Root              CurrentLocation PSComputerName
---- --------- --------- -------- ----              --------------- --------------
A                                 A:\                               DC01
C        12.91     26.75          C:\  Users\tommymaynard\Documents DC01
D                                 D:\                               DC01

This example of Invoke-Command connected to the remote computer DC01, ran a filtered Get-PSDrive command, returned the results to my computer, and then were formatted by the Format-Table cmdlet.

Now, let’s say we have… 23 computer names stored in the variable $Computers and we want to run the command we used above, against each computer. What so many people seem to do is wrap Invoke-Command in a Foreach construct (think: loop). I’m guessing, because I’ve made the error myself, that it’s because someone learned about Foreach first. Like I said, I discovered my error on my own, and only moments later. It didn’t wake me at 2 a.m. in a cold sweat after two months, which could’ve been possible.

Here’s how not to do this, and again, $Computers is holding the names of 23 computers.

Foreach ($Computer in $Computers) {
    Invoke-Command -ComputerName $Computer -ScriptBlock {Get-PSDrive -PSProvider FileSystem}
}
Name           Used (GB)     Free (GB) Provider      Root                                CurrentLocation PSComputerName
----           ---------     --------- --------      ----                                --------------- --------------
C                  20.47         59.18               C:\                                 ...rd\Documents DC02
D                                                    D:\                                                 DC02
A                                                    A:\                                                 WEB01
C                  50.36         29.30               C:\                                 ...rd\Documents WEB01
D                                                    D:\                                                 WEB01
A                                                    A:\                                                 SQL01
C                  53.59         26.06               C:\                                 ...rd\Documents SQL01
D                                                    D:\                                                 SQL01
... # Not all results included.

What we’ve done here is force Invoke-Command to run against only one computer at a time. During each iteration of the Foreach loop, Invoke-Command runs against the computer name currently stored in $Computer. This variable is updated to the next computer name in the variable at the start of each loop. It’s like this: connect to DC02 and run the command. Now, connect to WEB01 and run the command. Are you done, Invoke-Command? Okay then, now Foreach says to run the same command against the computer SQL01. That’s not how this cmdlet was designed to be used.

Before we go any further, I wrapped this command inside the Measure-Command cmdlet to determine how long it took my system to run this against the computers in $Computers. It took nearly 30 seconds. While that’s not an eternity, we’re only talking about 23 computers, and we’re only talking about a single command.

Measure-Command -Expression {
    Foreach ($Computer in $Computers) {
        Invoke-Command -ComputerName $Computer -ScriptBlock {Get-PSDrive -PSProvider FileSystem}
    }
}
Days : 0
Hours : 0
Minutes : 0
Seconds : 27
Milliseconds : 513
Ticks : 275132689
TotalDays : 0.000318440612268519
TotalHours : 0.00764257469444444
TotalMinutes : 0.458554481666667
TotalSeconds : 27.5132689
TotalMilliseconds : 27513.2689

Now, let’s set this command up the proper way. The ComputerName parameter of Invoke-Command can take a collection of computers. It should be said that many cmdlets can take more than one computer as a value to their ComputerName parameter. The difference is that Invoke-Command can run against all of the 23 computers at the same time. In fact, the default throttle limit—the number of computers in which the command will run against simultaneously—is 32. It can be changed by including the ThrottleLimit parameter, such as -ThrottleLimit 50 or -ThrottleLimit 15.

Now, here’s how this command should’ve been written.

Invoke-Command -ComputerName $Computers -ScriptBlock {Get-PSDrive -PSProvider FileSystem}
Name           Used (GB)     Free (GB) Provider      Root                                CurrentLocation PSComputerName
----           ---------     -------u-- --------      ----                                --------------- --------------
A                                                    A:\                                                 DC02
A                                                    A:\                                                 WEB01
C                  41.72         37.93               C:\                                 ...rd\Documents WEB01
C                  41.13         38.52               C:\                                 ...rd\Documents DC02
D                                                    D:\                                                 DC02
D                                                    D:\                                                 WEB01
A                                                    A:\                                                 SQL01
C                  46.75         32.91               C:\                                 ...rd\Documents SQL01
D                                                    D:\                                                 SQL01
... # Not all results included.

I measured this command five times, and the average of those five runs was only 2.8 seconds. Remember, the Foreach loop took almost 30 seconds to get the same results. This is due to the fact that, again, Invoke-Command will work with the remote computers simultaneously (when it’s not inside a Foreach). There’s never any waiting to run the command on any additional computers unless there’s more computers than the value of ThrottleLimit.

Please keep the topic in this post in mind, as it’s possible to momentarily forget this feature just long enough to wrap the Invoke-Command cmdlet in a Foreach, or other looping construct.

 

Determine If There’s a Current PowerShell Remoting Session

Ever need to reboot a server and worry someone else may be actively logged on? In this situation, you can either check Task Manager > Users, or use the quser.exe command-line tool. If you’re not familiar with the command-line tool quser.exe, it can be used against a remote computer, as in the example below.

PS> quser.exe /server DC01
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 adminbob                                  2  Disc      2+01:17  1/29/2016 8:58 PM

You can use this command-line tool on a local computer, as well, in order to see if anyone else is logged on. All you need to do is enter quser.exe (drop the /server switch [which indicates the remote server to query]), and the remote computer name. Since quser.exe is located in C:\Windows\System32, you don’t even need to include the .exe. The Path variable, $env:PATH, will assist in correctly determining the executable.

The problem is that this command-line tool, and Task Manager, don’t tell us if anyone has a current PowerShell Remoting session to the server. That’s where the Get-WSManInstance cmdlet can help. Here’s an example of using this cmdlet to check our DC01 server — the same server as in the example above.

PS> Get-WSManInstance -ComputerName DC01 -ResourceURI Shell -Enumerate
PS>

Because it didn’t return any information, we can safely assume there are no active PS Remoting sessions on DC01. Had there been, it would have returned something like the results below.

PS> Get-WSManInstance -ComputerName DC01 -ResourceURI Shell -Enumerate

rsp             : http://schemas.microsoft.com/wbem/wsman/1/windows/shell
lang            : en-US
ShellId         : AB4D6A3B-213B-20F6-A61C-9CCAG41A1C2E
Name            : Session1
ResourceUri     : http://schemas.microsoft.com/powershell/Microsoft.PowerShell
Owner           : MYDOMAIN\adminbill
ClientIP        : 10.10.10.20
ProcessId       : 10192
IdleTimeOut     : PT7200.000S
InputStreams    : stdin pr
OutputStreams   : stdout
MaxIdleTimeOut  : PT2147483.647S
Locale          : en-US
DataLocale      : en-US
CompressionMode : XpressCompression
ProfileLoaded   : Yes
Encoding        : UTF8
BufferMode      : Block
State           : Connected
ShellRunTime    : P0DT0H0M6S
ShellInactivity : P0DT0H0M4S
MemoryUsed      : 70MB
ChildProcesses  : 0

The default properties are much more than I need. Here’s a filtered down version of the cmdlet’s properties using the Select-Object cmdlet. In my opinion, these properties are the ones that include the most desirable information.

PS> Get-WSManInstance -ComputerName DC01 -ResourceURI Shell -Enumerate |
>>> Select-Object Name,Owner,ClientIP,State

Name         Owner                  ClientIP        State
----         -----                  --------        -----
Session1     MYDOMAIN\adminbill     10.10.10.20     Connected

It seems I have a difficult time remembering the required parameters in this command; therefore, I wrapped the whole thing in a function and placed it in my profile. This, in order to make sure that I’ll never remember them. My function also allows me to run this command against multiple remote computers at nearly the same time. The -ComputerName parameter of Get-WSManInstance only accepts a single computer per execution, so wrapping this cmdlet in a Foreach construct (within the function), allows us to work around this behavior. Take a look at the function and the example below, as the function runs against three different computers.

Function Get-PSRemotingSession {
    Param(
        [string[]]$ComputerName
    )

    Foreach ($Computer in $ComputerName) {
        Get-WSManInstance -ComputerName $Computer -ResourceURI Shell -Enumerate |
            Select-Object -Property @{N='ComputerName';E={$Computer}},Name,Owner,ClientIP,State
    }
}
PS> Get-PSRemotingSession -ComputerName DC02,WEB01,DC03

ComputerName : DC02
Name         : Session1
Owner        : MYDOMAIN\admindave
ClientIP     : 10.10.10.22
State        : Connected

ComputerName : DC03
Name         : Session1
Owner        : MYDOMAIN\admindave
ClientIP     : 10.10.10.22
State        : Connected

These results indicate that two of the three computers have active PS Remoting sessions. In this case, the two sessions were initiated by MYDOMAIN\admindave. The IP is not the remote computer’s IP, such as DC02 and DC03, but instead the IP from the connecting computer (Dave’s computer).

Keep this cmdlet in mind when you’re doing anything that will restart the WinRM service, to include restarting the server. Restarting this service on a computer where there’s an active PS Remoting session, will instantly break the remoting session.

Why is there a tommymaynard.com?

If someone told me a couple years ago that I would have my own Windows PowerShell website by now, I might’ve thought they were crazy. It was back then when I was relatively new to PowerShell. I had pieced together a few scripts by then, but I wasn’t anywhere near where I am now. This isn’t to imply I know it all; I’m certainly still learning something every day — that’s actually the way I became successful with PowerShell.

But, here I am. It’s just over 20 months now, that I’ve had my own site, and have been sharing scripts, providing some quick learning, and generally, talking about PowerShell in one way or another. Prior to my most recent position, I was spread too thin; I knew a little about a lot. Today, I know a lot about only a handful of technologies, allowing me to gain a deeper perspective and understanding. My knowledge of PowerShell proves this.

Yesterday, I received a PM on Reddit. That’s happens a fair amount of the time, as it is one of the places I go to learn and read, and assist with PowerShell questions when I can. While these are typically comment replies, this one was a direct message, straight to me, from some unknown person. Here’s a screen capture of the message.

The tommymaynard.com Website01

First off, I don’t get anything tangible from this website. In some ways, it’s just a storage for things I may want to reference later. More so, however, it’s to give people that want PowerShell content to have another place to consistently find it. I remember those nights, not quite ready to fall asleep and looking for some PowerShell content to read on Twitter. I often found something, and then learned something in the final minutes before sleep. This actually led to several dreams about PowerShell, which was fine. I think I tried to convince myself I was learning in my sleep, too. The posted articles on Twitter made me successful, in that they provided an opportunity for continual learning. At some point I decided I wanted to give back.

All of this to say, that while this site isn’t lining my pockets in cash, it’s serving a purpose, and now I know it for sure. It’s refreshing to know that I’m not writing to myself. That said, hopefully it’s more than just me and “that person from Reddit.” I think it is; I think this site is truly helping people learn PowerShell, and that right there, is my reward. I’m happy that I get to play a part in helping current, but most likely new, members of the PowerShell community.

In closing, I’m reminded of the final Microsoft TechEd Conference in 2013 in New Orleans. I was in this giant room — maybe even at two different times — to hear Don Jones and Jeffrey Snover speak. I didn’t really know who these speakers where then. There must’ve been several hundred people in that same room waiting to hear Don and Jeffrey. Out of that entire room full of people, here I am, writing and publishing PowerShell content. I’ve included my notes from those sessions below. Based on the notes, you can likely judge how little I knew then. I had to write down Out-GridView, as I hadn’t heard of the cmdlet yet. Actually, I only wrote Out-Grid; I’m glad I didn’t get hung up on that one later.

The-tommymaynard.com-Website02

The-tommymaynard.com-Website03

The-tommymaynard.com-Website04

PowerShell + DevOps Global Summit 2016

For a moment, my worst fear was being realized. Okay, maybe not my absolute worse, but it felt bad. Back to this in a moment.

I was in attendance at the PowerShell Summit North America 2015, when I already knew I wanted to return to the 2016 summit. It was the first day and probably only 10 minutes into Don’s welcome. I met so many great people; I talked with and enjoyed the company of many talented and like minded individuals. I heard amazing speakers. It really was as great an opportunity as I thought it would be. If you’re reading this, and you’ve been, you know. If you’ve yet to go, then trust me, you’ll want this experience, too.

I asked my lead at work if I would be able to attend again this year, and what I heard was that  our budget may not have any room in it for a return trip. It wasn’t a sure thing in either direction. With doubt in mind, I was excited to see a contest by PowerShell.org. They offered a Free 4-Day Pass to the summit for the best article submitted for their TechLetter. After reading a forum topic on a PowerShell-related forum, I decided I would borrow the idea and write about my proudest moment in PowerShell — using constrained endpoints to reduce account elevation — and send it in.

Waiting for the January PowerShell.org TechLetter to show up took forever. As all submitted articles were due by the end of November 2015, December turned out turned out to be a long month. In addition, the TechLetter doesn’t usually arrive until mid month. I didn’t really think I had a chance. It’s not like I wasn’t going to check, however. Well, on the early morning of Tuesday, January 19th, half asleep, I opened my inbox and there it was: the January TechLetter.

Unbelievably, it said I had won. Oh, God. My first thought was disbelief (I kept having that thought), followed by feeling bad for everyone else that submitted an article, and didn’t win. I’m certain there were plenty of articles that were great, and I’m sure I’ll get to read them all in upcoming TechLetters — getting an article in there alone, is an honor in my mind. I’m still mystified that I can write about PowerShell and have it deemed worthy for the community.

So, my fear was that I might win the 4-day pass and still not be able to get the financial backing of my employer. A short time after my morning shower, I wrote an email, and later got on the phone with my lead (as I was working remotely that day), to tell her the news. Well, it was a few days later, after a night where my wife and I determined they wouldn’t fund the summit on my behalf, that I got the good news. I was going to be able to attend!

I get it’s the second go around for me, but I’m still quite excited. The venue location is going to be great (likely better than last year), and the content is going to be top notch. I want to do BBQ with Dave again, and I’ve got to have dinner with Josh again (Warren F. — are you going to be there?). This is going to be just as, if not more, rewarding than last time.

So, join in if you can. I’m glad I get to be there; it feels like it was a close one.

The $PSBoundParameters Automatic Variable

I had a recent run in with $PSBoundParameters and thought, hey, I should write about that. $PSBoundParameters is an automatic variable. You can read more about it, and others, in Get-Help -Name about_Automatic_Variables, but before you do, let’s chat about it here. This variable contains key-value pairs of the parameters and the corresponding values that are passed to a function, or script. Consider the function declared below.

Function Show-Parameter {
    Param (
        [string]$Text,
        [int]$Number
    )

    $PSBoundParameters
}

Now that we have our function declared, we can invoke it (think, run it), by entering its name.

PS> Show-Parameter
PS> # ^ No parameters and values included,...
PS> # so the function does nothing.
PS> Show-Parameter -Text 'hi'

Key                   Value
---                   -----
Text                  hi

PS> Show-Parameter -Text 'hello' -Number 5

Key                   Value
---                   ----
Text                  hello
Number                5

PS> Show-Parameter -Number 10

Key                   Value
---                   -----
Number                10

It’s important to remember that all our function did was echo the value(s) stored in $PSBoundParameters. Now that we know we can do this, why might we want to do this? In scripting, we often need to make decisions based on information we’ve collected, and in this case, we make our decisions based on whether or not a parameter was used when a function was invoked. Let’s modify the function first and run it a few more times.

Function Show-Parameter {
    Param (
        [string]$Text,
        [int]$Number
    )

    If ($PSBoundParameters.ContainsKey('Text')) {
        Write-Output -InputObject "Text has been included as: '$Text'"
    }

    If ($PSBoundParameters.ContainsKey('Number')) {
        Write-Output -InputObject "Number has been included as: '$Number'"
    }
}

Now our function, when invoked, will take a specific action when a parameter(s) is included. Here’s a few examples.

PS> Show-Parameter -Text 'sample text'
Text has been included as: 'sample text'
PS>
PS> Show-Parameter -Number 20
Number has been included as: '20'
PS>
PS> Show-Parameter -Text 'more sample text' -Number 25
Text has been included as: 'more sample text'
Number has been included as: '25'

Here’s one final modification. In the example below, we’ve removed the -Number parameter and the If statement that checks if that parameter was included. Then, we added a switch statement inside the If statement that checks if the -Text parameter was included. Based on the value provided to the -Text parameter, we’ll take specific actions (up to a point). If the string ‘hello’ is included as the parameter’s value, we’ll reply with one message, if the string ‘hi’ is included as the parameter’s value, we’ll reply with another message, and if anything else is included, we’ll simply reply with the value provided to the parameter.

Function Show-Parameter {
    Param (
        [string]$Text
    )

    If ($PSBoundParameters.ContainsKey('Text')) {
        switch ($Text) {
            {$_ -eq 'hello'} {Write-Output -InputObject 'Hello to you, as well.'; break}
            {$_ -eq 'hi'} {Write-Output -InputObject 'Hi.'; break}
            default {"You entered: $Text"}
        }
    }
}

With $PSBoundParameters, we can check if a parameter is used, as well as, work with the value that’s provided to that parameter. Below are three examples of using the above function.

PS> Show-Parameter -Text hi
Hi.

PS> Show-Parameter -Text hello
Hello to you, as well.

PS> Show-Parameter -Text adios
You entered: adios

That’s it. Enjoy the rest of the weekend and have a great week!

Proving PowerShell’s Usefulness to Newbies, Part III

Here we go! It’s the third time doing what we can, to try and prove the usefulness and power in Windows PowerShell. With a little work, those new in PowerShell, get a glimpse into not only what they can do with PowerShell, but also why they’d want to do it with PowerShell. In the end, most of this is about time savings, and tonight’s post is no exception.

Part I can be found here. In that post, we learned that we could use PowerShell to create 10,000 folders in about 10 seconds — that’s 1,000 folders per second, or 1 folder each millisecond.

Part II is here. This post was about reading in a file’s contents, sorting the contents alphabetically, and then writing the sorted results back to the same file. PowerShell took milliseconds, while my manual attempt took minutes.

Part III
As part of my imaginary Windows PowerShell presentation I thought I might give one day, I always pictured a common theme — saving time. That’s a big part of why many of us have taken so fondly to PowerShell. I pictured telling a story about a few different approaches to the same problem, all to be completed by a different admin, each with their own best way of handling the request. Here’s my hypothetical, yet quite possible situation, beginning with the characters.

Admin One: This admin is fairly new to the industry. While they know their way around a desktop version of Windows, they’re still often learning new things about it. While they’re motivated, they haven’t learned enough to often know better.

Admin Two: This admin has a year or two on admin one, and is quickly picking up better ways to speed things up by utilizing certain tools.

Admin Three: This admin is getting there. It’s almost daily that they’re figuring out faster, and more remote ways to complete tasks. While they aren’t scripting and running commands (yet), they do know some tricks to get their results quicker.

Admin Four: This admin has been studying all things PowerShell whenever there’s a moment for it. Every task assigned is another challenge to learn more PowerShell, and to continue to add to their skills. They’re freeing up time, all the time.

One day the boss comes in with a request of the team. It’s not a difficult task. In fact, any one of the team members can get the task done. They need to check 200 of their computers to determine, and then document, which of the computers are running the print spooler service. Maybe it’s just busy work handed down by the boss, or maybe this is a valid concern somehow.

Admin One gets his clipboard and heads down to one of the computer labs. Once the three labs are checked, he’ll head to each of the offices to check those computers manually, too. Here’s the steps this admin takes: (1) Log on to the computer, (2) Open Computer Management, (3) Click on Services, (4) Find the Print Spooler service, noting the status, and (5) Write down the computer name and service’s status.

Let’s assume Admin One takes 5 minutes per computer which includes the time spent walking from computer to computer and office to office. At 200 computers, that’s 1,000 minutes, or approximately 16.6 hours. That’s longer than two full 8 hour days at the office. That kind of time loss equates to zero time spent learning anything new for two days in the week. You can’t stay in the field using this procedure.

Admin Two’s first though is Remote Desktop. While this admin still has to log on, it can be done without physically visiting each computer. In addition, this admin can work with more than one remote computer at a time. This allows Admin Two the ability to complete one computer every 2 1/2 minutes, cutting the time it takes Admin One in half. That’s only a little over one work day — 8.3 hours — lost to this project. While it’s shorter, it’s still too much time.

Admin Three knows enough to know that they can connect to a remote computer from inside Computer Management, which means they can also avoid leaving their desk. This removes the step of interactively logging on to a computer, and loading a desktop environment, too. We’ll assume this admin can do one computer every minute. That’s 200 total minutes, or 3.3 hours. That admin can actually finish before lunch if they start soon enough in the day. This might be reasonable by some boss’ standards, but PowerShell has been designed to do this quicker.

The final admin, Admin Four, knows PowerShell, and they know it well enough to pull back all those results without the need for any GUIs. There’s no wandering around the building, writing things down to a clipboard, or even entering data into a file, as Admin Two and Three do from their desks, as they use RDP and Computer Management, respectively.

Admin Four has a couple approaches and plenty of time to try them both out. While there’s many ways to read in computer names, we’ll assume they have a list of the 200 computers in a text file with each computer name on its own line. They may have had to build this file, but if they’re pulling from Active Directory, then it was probably automated, as well.

The first option is to use the Get-Service cmdlet, as seen below, in the first part of the example. The second part, which requires PowerShell Remoting, uses Invoke-Command. I’ve only include 20 computers in the computers.txt file, and to save space, only included 5 results per command in the example.

PS> # Uses the Get-Service cmdlet with -ComputerName parameter.
PS> # Have to add the MachineName property to see which results goes with which computer.
PS> Get-Service -ComputerName (Get-Content -Path .\computers.txt) -Name spooler | Select-Object MachineName,Status,Name,DisplayName

MachineName                                          Status Name                          DisplayName
-----------                                          ------ ----                          -----------
DC01                                                 Running spooler                       Print Spooler
DC02                                                 Running spooler                       Print Spooler
DC03                                                 Running spooler                       Print Spooler
WEB01                                                Running spooler                       Print Spooler
WEB02                                                Running spooler                       Print Spooler
...

PS> # Uses Invoke-Command which automatically adds the name of the remote computer by default.
PS> Invoke-Command -ComputerName (Get-Content -Path .\computers.txt) -ScriptBlock {Get-Service -Name spooler}

Status   Name               DisplayName                            PSComputerName
------   ----               -----------                            --------------
Running  spooler            Print Spooler                          SQL01
Running  spooler            Print Spooler                          DC02
Running  spooler            Print Spooler                          DC02
Running  spooler            Print Spooler                          WEB02
Running  spooler            Print Spooler                          DC03
...

While I didn’t include the results, each of these two options — Get-Service, and Get-Service inside Invoke-Command — took about the same amount of time when they were measured. Even so, we’ll round up and say it takes 10 second for each set of 20 computers. At 200 computers, that 100 seconds for all of them. At 100 seconds, we’re talking about an approximation of 1 1/2 minutes to check all two hundred computers.

If I was in front of a crowd right now, and had just talked thought this post, I’d take a moment to ask the crowd which admin they thought would be around after budgets cuts. The admin that showed up with the results written down, after two days, or the one who sent the boss the results via email after two minutes? Keep in mind, that either of these cmdlets above can be used in conjunction with Export-Csv — a cmdlet that produces a text-based file that can be opened and edited with Microsoft Excel — something in which the boss probably has on their computer. In addition to automatically writing the results to file, by using the Send-MailMessage cmdlet, Admin Four can also automate sending the results by email.

Proving PowerShell’s Usefulness to Newbies, Part II

Back again with another example to share with the Windows PowerShell newbies. They’re out there: people that don’t know the practical power in PowerShell. The idea is to use real-world examples of things we simply wouldn’t want to do manually, in hopes that our lost friends will find the light. Maybe, that’s you.

Part I can be found here. In that post, we learned that we could use PowerShell to create 10,000 folders in about 10 seconds — that’s 1,000 folders per second, or 1 folder each millisecond.

Part II
What we’ll do today is read in the content of a file, sort the items we’ve read in, and then write our sorted results back out to the same file — it’s instant alphabetizing. Let’s start by reading in our file and see what we’re starting with.

PS> Get-Content -Path .\file.txt
web02.mydomain.com
web09.mydomain.com
dc03.mydomain.com
dc04.mydomain.com
sql08.mydomain.com
sql06.mydomain.com
dc08.mydomain.com
sql04.mydomain.com
dc10.mydomain.com
web01.mydomain.com
web03.mydomain.com
dc09.mydomain.com
web05.mydomain.com
web06.mydomain.com
dc02.mydomain.com
web07.mydomain.com
dc05.mydomain.com
web04.mydomain.com
web10.mydomain.com
sql01.mydomain.com
dc01.mydomain.com
sql03.mydomain.com
sql10.mydomain.com
web08.mydomain.com
sql05.mydomain.com
sql07.mydomain.com
dc07.mydomain.com
sql02.mydomain.com
sql09.mydomain.com
dc06.mydomain.com

Our file contains 30 fully-qualified servers names that are in no significant order. Sorting all 30 of these server names is probably going to be difficult, or at least, it’s going to be time consuming. Wrong. With one quick addition to the previous command — see the example below — our list is sorted and without any noticeable difference in the amount of time it took to complete.

PS> Get-Content -Path .\file.txt | Sort-Object
dc01.mydomain.com
dc02.mydomain.com
dc03.mydomain.com
dc04.mydomain.com
dc05.mydomain.com
dc06.mydomain.com
dc07.mydomain.com
dc08.mydomain.com
dc09.mydomain.com
dc10.mydomain.com
sql01.mydomain.com
sql02.mydomain.com
sql03.mydomain.com
sql04.mydomain.com
sql05.mydomain.com
sql06.mydomain.com
sql07.mydomain.com
sql08.mydomain.com
sql09.mydomain.com
sql10.mydomain.com
web01.mydomain.com
web02.mydomain.com
web03.mydomain.com
web04.mydomain.com
web05.mydomain.com
web06.mydomain.com
web07.mydomain.com
web08.mydomain.com
web09.mydomain.com
web10.mydomain.com

Yeah, that was tough. I ran this same command 10 times, measuring it with the Measure-Command cmdlet, so I could see the average amount of time it took to sort the list of computers. It completed this “challenge” in anywhere between 6 and 10 milliseconds. I’m sorry, but it would’ve taken me at least a few minutes do to this by hand.

Now that we know we can sort it, let’s write it back to disk. In this example, below, we’ll put our sorted results back into the file where it came from, overwriting the original contents. If you don’t want to do that, then be sure to alter the file name value for Set-Content’s -Path parameter.

PS> Get-Content -Path .\file.txt | Sort-Object | Set-Content -Path .\file.txt
PS>

Let’s verify that our file now contains the sorted contents by rerunning our first Get-Content command from the beginning of this post.

PS> Get-Content -Path .\file.txt
dc01.mydomain.com
dc02.mydomain.com
dc03.mydomain.com
dc04.mydomain.com
dc05.mydomain.com
dc06.mydomain.com
dc07.mydomain.com
dc08.mydomain.com
dc09.mydomain.com
dc10.mydomain.com
sql01.mydomain.com
sql02.mydomain.com
sql03.mydomain.com
sql04.mydomain.com
sql05.mydomain.com
sql06.mydomain.com
sql07.mydomain.com
sql08.mydomain.com
sql09.mydomain.com
sql10.mydomain.com
web01.mydomain.com
web02.mydomain.com
web03.mydomain.com
web04.mydomain.com
web05.mydomain.com
web06.mydomain.com
web07.mydomain.com
web08.mydomain.com
web09.mydomain.com
web10.mydomain.com

That’s it. We ran a one-liner command to read in some data, sort it, and push it right back to the same file. So we were able to compare, I went ahead and sorted the same, 30-line file manually. It took me 3 minutes and 30 seconds (proof below). While this wasn’t the most complex, or lengthy document one might sort, the time difference is still quite drastic. PowerShell has plenty to offer, and spending some time to learn it is going to pay off. This line of work requires all the time you have to continue to learn, and stay relevant. PowerShell can give you some time back.

PS C:\> $StartTime = Get-Date
PS C:\> $EndTime = Get-Date
PS C:\> New-TimeSpan -Start $StartTime -End $EndTime


Days              : 0
Hours             : 0
Minutes           : 3
Seconds           : 30
Milliseconds      : 860
Ticks             : 2108600828
TotalDays         : 0.00244051021759259
TotalHours        : 0.0585722452222222
TotalMinutes      : 3.51433471333333
TotalSeconds      : 210.8600828
TotalMilliseconds : 210860.0828

Proving PowerShell’s Usefulness to Newbies, Part I

Months ago, I started writing a list of ways to potentially impress PowerShell newbies for a presentation (I’ve yet to ever give). Since I’m not sure if I’ll be up in front of a crowd of PSNewbies any time soon, I figured I would share them here. Consider these when you need to impress the PowerShell non-believers.

Part I
The power in PowerShell has been said to be a number of different things, by a number of different people. In today’s topic, the power is speed and accuracy. We’re going to automate the creation of 10,000 directories (a.k.a. folders). That’s no small task if you’re doing it manually.

Beginning in the example below, we’ll start by creating a single directory to hold the other 10,000 directories, by running a New-Item command. Once that’s finished, which is practically instantaneous, we’ll run second command to create the 10,000 new folders. We’ll do this by using a range operator (..) and the numbers 1 and 10000, piping each of those numbers and the ones in between, to that second New-Item command.

Notice the $_ variable, as this has the tendency to cause some confusion. This variable holds the value of the current object that’s entered, or crossed, the pipeline — you pick your visual. In this case, it represents the current number from within our range of numbers. The first time this command runs, $_ will be set to 1, the second time this runs it will be set to 2, and the last time it runs, it will be set to 10000. I should note, that in PowerShell 3.0, the $PSItem variable was introduced. It can be used in place of $_ in version 3.0 and above.

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

    Directory: C:\

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----          1/6/2016   9:52 PM            Testing

PS> 1..10000 | ForEach-Object {New-Item -Path "C:\Testing\$_" -ItemType Directory}

    Directory: C:\Testing

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----          1/6/2016   9:52 PM            1
d----          1/6/2016   9:52 PM            2
d----          1/6/2016   9:52 PM            3
d----          1/6/2016   9:52 PM            4
d----          1/6/2016   9:52 PM            5
d----          1/6/2016   9:52 PM            6
d----          1/6/2016   9:52 PM            7
d----          1/6/2016   9:52 PM            8
d----          1/6/2016   9:52 PM            9
d----          1/6/2016   9:52 PM            10
...

I haven’t included all 10,000 results, for good reason, but as you can see, the moment the command was entered into the PowerShell host, it began to create the new folders. This would be the time at which you retie your shoes. The example further below indicates the command takes about 10 seconds to complete. Depending on your sneakers, if you’re wearing those, you might actually take longer. If you don’t have laces, take a drink of something, or simply sit there in amazement at all the work you’re not really doing. It might actually be too fast, once you’ve realized how much time you just freed up.

A little note about Measure-Command used below: Typically this cmdlet will internally measure things and not really do much that you can actually see. In this case, it’s actually going to create the folders as part of the measurement. To me, it makes sense that it wouldn’t be able to measure this unless it really creates the folders. On that note, if you ran the command above, you’ll want to delete the existing folders, if you want to measure how long it takes to create the folders. The New-Item cmdlet can’t create folders that already exist. To remove the directories, run:  Remove-Item -Path ‘C:\Testing\*’ from your PowerShell console.

PS> Measure-Command -Expression {1..10000 | ForEach-Object {New-Item -Path "C:\Testing\$_" -ItemType Directory}}

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 10
Milliseconds      : 293
Ticks             : 102939103
TotalDays         : 0.000119142480324074
TotalHours        : 0.00285941952777778
TotalMinutes      : 0.171565171666667
TotalSeconds      : 10.2939103
TotalMilliseconds : 10293.9103

PS> Remove-Item -Path C:\Testing\*

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 9
Milliseconds      : 793
Ticks             : 97934962
TotalDays         : 0.000113350650462963
TotalHours        : 0.00272041561111111
TotalMinutes      : 0.163224936666667
TotalSeconds      : 9.7934962
TotalMilliseconds : 9793.4962

In the second command above, we actually measured the time it took to remove the directories we created — you saw that command a moment ago. It came in at a faster time than it took to create them, although this won’t always be the case. It’s still quite quick for either operation.

This is a great example of the power in PowerShell, but before I wrap up, let’s see how much time I just saved. Let’s assume it takes me 4 seconds to manually create a folder. To create 10,000 folders, I would need a free 40,000 seconds. To put that into hours, I would need over 11 of them to do this task by hand. We only get one person’s salary, but we should probably get a few.

PS> $SecondsToCreateFolder = 4
PS> $NumberOfFolderToCreate = 10000
PS> $NumberOfFolderToCreate * $SecondsToCreateFolder
40000
PS> $SecondsInMinutes = $MinutesInHours = 60
PS> $SecondsInMinutes
60
PS> $MinutesInHours
60
PS> ($NumberOfFolderToCreate * $SecondsToCreateFolder) / $SecondsInMinutes / $MinutesInHours
11.1111111111111

Thanks for reading this post.

Add, Concatenate, and a Little More

My desire to know Windows PowerShell from beginning to end isn’t just for me. It’s also for those around me — online, and in real life. I want to be a go-to-guy for all things PowerShell. I’ve found something I really love about the industry right now, and so I want know it well, and share it often. I truly believe that learning the fundamentals of PowerShell make one much more prepared to use the language interactively, as well as inside scripts and functions. I’ve said it before: It’s easy to spot someone that doesn’t know the fundamentals of PowerShell, and is only out to learn as much as is required to complete their current task.

A couple weeks ago a coworker asked me why their conditional wasn’t working (think, the part inside the parentheses in an If statement, for example). While I never saw the code, as the conversation happen over cube walls and by instant message, I suspected they were trying to compare a numeric value with something that wasn’t numeric. It turned out I was right. I determined this, the moment they said they had quotes around their numeric value.

It’s simple. The moment we put numeric values inside quotes — single or otherwise — we are no longer working numbers — we’re working with strings. Take a look at the example below. In the first command, we use addition to add the numeric values 5 and 10, which results in the (numeric) value of 15. In the second command, we concatenate (think, combine or join) — using the same operator (+). When we concatenate the string value of 5 with the string value of 10, we create the (string) value of 510.

PS> 5 + 10
15
PS> '5' + '10'
510

The information I sent to my colleague by IM is listed below. I thought it would best explain the difference between a “numeric value” inside quotes, and one not inside quotes. In this example, 123 is an integer (System.Int32) without quotes around it, and a string (System.String) with quotes around it. A quick note about Select-Object’s -Unique switch parameter: This parameter returns duplicated results once, and only once. Had this not been included, it would have returned the same result for each member of the object (each method, property, etc.).

PS> 123 | Get-Member | Select-Object TypeName -Unique

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

PS> '123' | Get-Member | Select-Object TypeName -Unique

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

In our first example in this post, we saw what happens when the add two numeric values and what happens when we concatenate two strings. If you think like me, you’re probably wondering what happens when we concatenate a string value to a numeric value, and add a numeric value to a string character. It depends. Take a look at the slightly, modified example below, and then we’ll discuss it.

PS> 5 + '10'
15
PS> '5' + 10
510

Did you figure it out what’s happened in those examples? In the first command, 5 + ’10’, the numeric on the left of the equation, has forced the string on the right side, to switch from a string to a numeric data type. With two numeric values, it then added the two values and returned 15.

The second command, or equation, works much like the first one, except in reverse. The string character 5 on the left, forces the numeric value on the right into being a string and then concatenates the two, which results in 510 again. Whatever is on the left is going to try and change the type of whatever is on the right — when it can. So, when can’t it?

PS> 5 + '1a2b3c'
Cannot convert value "1a2b3c" to type "System.Int32". Error: "Input string was not in a correct format."
...
PS> '1a2b3c' + 10
1a2b3c10

The first command in the example above fails because it cannot convert the string, ‘1a2b3c,’ into a numeric value because it includes non-numeric characters — makes sense to me. Notice that this error helps prove what I said earlier: The data type of value on the left is being used to try and change the data type of the value on the right. The second equation works, because again, we’re simply joining, or concatenating, two values.

PS> 'Have a great ' + 2016 + '!'
Have a great 2016!

In closing, I’d like to add a word about the concatenation operation just above. While the plus sign, or rather, the concatenation operator does what it does well, it’s not used nearly as much as it was in the past for building strings — at least, I don’t see it much anymore. In fact, when I do see it, I almost always assume that what I’m looking at was written long ago and then borrowed. What you’re more likely to see these days, is in the example below.

PS> $Year = 2016 # It's a numeric value (no quotes).
PS> # Use double-quotes (below) to display the variable's value inside the string.
PS> "Have a great $Year!" # No concatenation operators needed!
Have a great 2016!

Thanks for the reading my first post of 2016!

PowerShell Resolutions 2016

I wrote about my Windows PowerShell resolutions last year after a Tweet by Boe Prox. In what may become a tradition, he’s started up the conversation up again, over at Reddit. If you’re not convinced to make a list for yourself, then let me recommend you do. Knowing I had written and shared a list of resolutions, and that people may have read it, was enough motivation to stick to what I could and update my progress during the year. My 2015 resolutions are listed in the first link above, and were an overall success.

So, 2016. First and foremost I’m out to be a DSC genius twelve months from now. I’ve been spending a decent amount of time adding new DSC resources, modifying configuration scripts, creating new MOFs and having target nodes pull those. While this is in test, I don’t see why I wouldn’t have rolled this out to production at some point in 2016.

It’s a fair bet to assume that I’ll continue to read, and help, on the PowerShell forums at PowerShell.org, Reddit, and Microsoft Technet. Instead of linking those individually, you can find their links on my about page. If you want to learn more about PowerShell, then I recommend you read these. It’s an easy way to pick up things you might not learn otherwise. In addition, you might start to find yourself helping others sooner, or later. It happened to me.

I’m going to read PowerShell in Depth quite soon, and PowerShell in Action, when the newest version ships in the spring. I really don’t mind reading what I already know for review and solidification of concepts. Plus, I want to be able to recommend these titles to people in situations where someone would benefit from them over some of the other PowerShell titles I’ve read. I can’t wait to get started.
Update: It’s not even 2016, and I went ahead and started PowerShell in Depth. I’ve read up through Part 1 of the book and I already appreciate the deeper level content. As someone that read the Month of Lunches book, I can easily spot where the authors provided additional information.

Let’s see, what else? How about I get my first module up on the PowerShell Gallery. I can do that in 2016, once I decided what to add, or what to first write and then add.

Almost as soon as I posted this, it occurred to me: Nano Server. I expect that I’ll take some time this year to become proficient with this technology.