Category Archives: Quick Learn

Practical examples of PowerShell concepts gathered from specific projects, forum replies, or general PowerShell use.

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.

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!

Prep computers.txt File for HTA

Last week, I opted to share a couple of old HTAs I had written. HTAs are HTML Applications and allow administrations the ability to create a graphical interface for their scripts. It’s an older technology and not something I still write, or use. Even so, I wanted to share them with the community. As they’re not Windows PowerShell-related, I thought I should circle back on that post and incorporate some PowerShell.

The second of the two HTAs I shared was called Remote Desktop Assistant (download link: RDAssistantv2.1 (9806 downloads ) ). Its purpose is to allow a user to select a computer description from a list and open Remote Desktop to connect to that computer. I know, I know, this goes against all things PowerShell, but it was written a long time ago. The HTA has a requirement for an external text file called computers.txt that stores computer descriptions and computer names / IP addresses, such as we have in the list below. It’s a computer description, a semi-colon, and the computer name or IP address.

computer1;10.10.10.5
computer2;dns1.mydomain.com
computer3;10.10.10.9

We can make use of Active Directory and PowerShell to create this list and subsequent text file, so we don’t have to do it manually. In fact, once you had your code written you could schedule it to ensure the computers.txt file was as accurate as the last time the scheduled task ran. While the preferred way to do this would be to store host names (at least in my opinion), I’ll show examples of collecting the IP addresses, too.

In the first example, we’ll pull all of our servers from a single Organizational Unit to return the names and DNSHostNames.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties DNSHostName | Select-Object Name,DNSHostName

Name                                                        DNSHostName
----                                                        -----------
SQL01                                                       SQL01.mydomain.com
SQL02                                                       SQL02.mydomain.com
SQL03                                                       SQL03.mydomain.com
SQL04                                                       SQL04.mydomain.com

In this example, we return the names and IP addresses.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties IPv4Address | Select-Object Name,IPv4Address

Name                                                        IPv4Address
----                                                        -----------
SQL01                                                       10.10.10.30
SQL02                                                       10.10.10.31
SQL03                                                       10.10.10.32
SQL04                                                       10.10.10.33

While these are the results we want, we need to get them into the proper format. We’ll do this by looping through each result and concatenating the two properties with semi-colon in between. To do this, we do not need to use the Select-Object cmdlet to return the Name and IPv4Address, or DNSHostName.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties IPv4Address | ForEach-Object {"$($_.Name):$($_.IPv4Address)"}
SQL01;10.10.10.30
SQL02;10.10.10.31
SQL03;10.10.10.32
SQL04;10.10.10.33

With the host name set, we’ll take this one step further and create our computers.txt file.

PS> Get-ADComputer -Filter * -SearchBase 'OU=Servers,DC=mydomain,DC=com' -Properties DNSHostName | ForEach-Object {"$($_.Name):$($_.DNSHostName)"} | Out-File -FilePath C:\computers.txt
PS> Get-Content -Path C:\computers.txt
SQL01;SQL01.mydomain.com
SQL02;SQL02.mydomain.com
SQL03;SQL03.mydomain.com
SQL04;SQL04.mydomain.com

Chances are good that if you use the DNSHostName, you’re never going to have an Active Directory computer object returned without one. The same can’t be said if you use the IPv4Address, as this property is created at the time the results are returned (it queries DNS). Think about it, have you ever seen an IPv4Address property inside Active Directory Users and Computer when viewing a computer object? The DNSHostName option might be the better option, but I’ll leave that up to you.

Search PowerShell Gallery Module for #Requires Statement

The first part of this post began on PowerShell.org. Start there, unless you already have: http://powershell.org/wp/2015/11/30/search-powershell-gallery-module-for-requires-statement.

Please use the connect.microsoft.com link here, or at the bottom of this post, to up vote my feedback.

Desired State Configuration (DSC) and Just Enough Administration (JEA) are two topics that have recently piqued my interest. This, after becoming involved in Windows PowerShell constrained endpoints, and writing proxy functions, to better control how cmdlets are used. While working with DSC and xJEA (x, as in experimental), I ran up against an error message in the Event Viewer on my Server 2012 R2 target node running Windows Management Framework (WMF) 4.0 (the installation package that contains PowerShell 4.0). Before I get too deeply involved in JEA in WMF 5.0 on Server 2016 — what appears to be constrained endpoints of the past, with the benefits of JEA, but without the need for DSC, and some additional new features — I wanted to ensure I was able to deploy JEA endpoints with DSC, while running WMF 4.0.

Knowing this, the first error message below, makes perfect sense. I downloaded a version of xJEA that had a PowerShell 5.0 requirement, without even knowing it. Instead of moving up to WMF 5.0 to “fix” this, I opted to first get an older version of xJEA and determine if I was able to make it run with a version that can be used on WMF 4.0. This seemed like a logical progression in my learning and understanding of both DSC and JEA. Here’s the error:

“Error Message = The script ‘MSFT_xJeaToolkit.psm1’ cannot be run because it contained a “#requires” statement for Windows PowerShell 5.0. The version of Windows PowerShell that is required by the script does not match the currently running version of Windows PowerShell 4.0.”

As to be expected, the error message that was reported in the console host, was much less helpful. In fact, I was at a total loss until I went hunting in the Event Viewer, where I turned up the previous error message. Here’s the error I saw in the console:

“Invoke-CimMethod : Failed to extract the module from zip file C:\Windows\TEMP\\635842583367244849\xJea_0.2.16.6.zip
downloaded by Download Manager WebDownloadManager.”

In order to determine which version of xJEA didn’t require PowerShell 5.0, I needed to download all the xJEA versions and inspect them. You see, there isn’t a way to find out if there’s a PowerShell version requirement… more on that shortly. To begin, I ensured that the PowerShell Gallery was a trusted repository, and therefore, wouldn’t prompt me when I ran the Save-Module command in an upcoming example. To do this, I ran the commands below to (1) verify if the PowerShell Gallery was trusted (which it wasn’t, and isn’t by default), (2) trust it as an installation source, and (3) verify it was trusted.

PS> Get-PSRepository

Name                      PackageManagementPro InstallationPolicy   SourceLocation
                          vider
----                      -------------------- ------------------   --------------
PSGallery                 NuGet                Untrusted            https://www.powershellgallery.com/api/v2/


PS> Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
PS> Get-PSRepository

Name                      PackageManagementPro InstallationPolicy   SourceLocation
                          vider
----                      -------------------- ------------------   --------------
PSGallery                 NuGet                Trusted              https://www.powershellgallery.com/api/v2/

When that was completed, what I needed was to download all the xJEA modules from the PowerShell Gallery, at once, and place them into a folder for each version. Luckily, the Save-Module cmdlet will handle the file structure. This means, each module (a DSC resource, in this case) will be saved inside a version folder, nested in the same xJea folder. I gave the cmdlet a base path (C:\), and it did the rest. Take a look below at the example and image.

PS> Find-Module -Name xJea -AllVersions |
>> ForEach-Object {Save-Module -Path C:\ -Name xJea -RequiredVersion "$($_.Version.ToString())"}

Search PowerShell Gallery Module for requires01

Once I had all the files downloaded, I needed to run through all the contained .psm1 files for the requires statement for PowerShell version 5.0. The command below returned 51 files where it located the string “requires -version 5.”

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path
C:\xJea\0.2.10\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.10\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.10\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.11\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.11\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.11\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.12\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.12\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.12\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.13\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.13\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.13\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.14\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.14\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.14\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.15\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.15\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.15\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.1\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.1\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.1\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.2\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.2\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.2\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.3\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.3\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.3\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.4\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.4\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.4\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.5\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.5\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.5\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.16.6\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.16.6\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.16.6\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.5\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.5\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.6\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.6\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.7\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.7\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.8\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.8\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.8\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
C:\xJea\0.2.9\DSCResources\Library\JeaAccount.psm1
C:\xJea\0.2.9\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
C:\xJea\0.2.9\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1

In the next two examples, I haven’t included all the results to save some space. The example below, takes the previous one, and removes the portion of the path to the left of the version number. Compare the last line in the example above with the last line in the example below, and you’ll see how the .Split() method cleaned up the beginning of each result.

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path |
>> ForEach-Object {$_.Split('\',3)[-1]}
0.2.10\DSCResources\Library\JeaAccount.psm1
0.2.10\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
0.2.10\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
0.2.11\DSCResources\Library\JeaAccount.psm1
0.2.11\DSCResources\MSFT_xJeaEndpoint\MSFT_xJeaEndpoint.psm1
0.2.11\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1
...
0.2.9\DSCResources\MSFT_xJeaToolkit\MSFT_xJeaToolkit.psm1

In this example, we added a second .Split() method to clean up the path information to the right of the version number. Now, we only have the versions, although we do have duplicates.

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path |
>> Foreach-Object {$_.Split('\',3)[-1].Split('\')[0]}
0.2.10
0.2.10
0.2.10
0.2.11
0.2.11
0.2.11
..
0.2.9

This example below, shows the full results again; however, each version is only listed one time. This is because we’ve piped the previous results to Select-Object -Unique. This means, for example, we only see 0.2.11 once, even though it appeared three times in the last example.

PS> (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' |
>> Get-Content) -match 'requires -version 5' |
>> Select-Object PSPath | Convert-Path |
>> ForEach-Object {$_.Split('\',3)[-1].Split('\')[0]} |
>> Select-Object -Unique
0.2.10
0.2.11
0.2.12
0.2.13
0.2.14
0.2.15
0.2.16
0.2.16.1
0.2.16.2
0.2.16.3
0.2.16.4
0.2.16.5
0.2.16.6
0.2.5
0.2.6
0.2.7
0.2.8
0.2.9

We now have a complete list of all the versions that require PowerShell 5.0. That means we can determine which versions will run with PowerShell 4.0.

In this final example, we set a variable, $VersionsRequireWMF5, to the results produced by our last command. These are the versions that require PowerShell 5.0. In the middle of the example below, we set a second variable, $AllVersions, to all the versions of xJEA that we downloaded. We did this by running a Get-ChildItem (think, dir or ls) against the C:\xJea directory and returned just the directory names. That gives us two variables that we can supply to the Compare-Object cmdlet. That cmdlet will tell us the differences between the values of the variables, indicating which versions I can use with WMF 4.0.

PS> $VersionsRequireWMF5 = (Get-ChildItem -Path C:\xJea -Recurse -Filter '*.psm1' | 
>> Get-Content) -match 'requires -version 5' | 
>> Select-Object PSPath | Convert-Path | 
>> ForEach-Object {$_.Split('\',3)[-1].Split('\')[0]} | 
>> Select-Object -Unique
PS>  
PS> $AllVersions = (Get-ChildItem -Path C:\xJea | Select-Object).Name
PS>  
PS> Compare-Object -ReferenceObject $AllVersions -DifferenceObject $VersionsRequireWMF5

InputObject SideIndicator
----------- -------------
0.2         <=
0.2.1       <=
0.2.2       <=
0.2.4       <=

Based on these results, version 0.2, 0.2.1, 0.2.2, and 0.2.4, do not require WMF 5.0 and can be tested with WMF 4.0, before moving to the newer version of WMF. You can expect that I’ll work with 0.2.4 tomorrow morning.

A final note: Microsoft may not yet think they need it, but they should indicate the version of PowerShell that is required by modules on the PowerShell Gallery. That needs to be a part of their approval process, and the results need to added to the web interface, and made a part of the results returned by the Find-Module cmdlet (see the full Find-Module results below). These results should include a Requires property. This isn’t going to be the last time someone comes up against this problem, especially as we move into versions post WMF 5.0.

PS> Find-Module -Name xJea | Select-Object *

Name : xJea
Version : 0.2.16.6
Description : Module with DSC Resources for Just Enough Admin (JEA). Jea makes it simple to create
custom RBAC solutions using PowerShell.
Author : Microsoft Corporation
CompanyName :
Copyright : (c) 2014 Microsoft Corporation. All rights reserved.
PublishedDate : 5/14/2015 7:51:23 PM
LicenseUri :
ProjectUri :
IconUri :
Tags : {PSModule, PSIncludes_DscResource}
Includes : {Function, DscResource, Cmdlet, Command}
PowerShellGetFormatVersion :
ReleaseNotes :
Dependencies : {}
RepositorySourceLocation : https://www.powershellgallery.com/api/v2/
Repository : PSGallery
PackageManagementProvider : NuGet

If you think this should be included, then up vote the feedback I’ve left on connect.microsoft.com.

Invoke a Command from a Get-History Menu

Sometimes you line up the right cmdlets, appreciate what you’ve done for a moment, only to become mildly irritated that you never thought of that before. I just did that.

We all know, or should rather, that the Get-History cmdlet returns a list of commands that have been entered during the current Windows PowerShell session. If you open up a new console, enter a couple commands, and then run Get-History, or one of its aliases (ghy, h, history), it’ll show you what commands you’ve entered up until that point. Take this example, for instance:

PS> Get-Process | Select-Object -Last 2

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    263      11     1680       5132    46     0.02   3356 WUDFHost
    227      12     1824       6804    47     0.22   3840 WUDFHost


PS> Get-Service | Select-Object -First 2

Status   Name               DisplayName
------   ----               -----------
Running  AdobeARMservice    Adobe Acrobat Update Service
Stopped  AdobeFlashPlaye... Adobe Flash Player Update Service


PS> Get-History

  Id CommandLine
  -- -----------
   1 Get-Process | Select-Object -Last 2
   2 Get-Service | Select-Object -First 2


PS> Get-History

  Id CommandLine
  -- -----------
   1 Get-Process | Select-Object -Last 2
   2 Get-Service | Select-Object -First 2
   3 Get-History

I use the up arrow quite often to cycle through my previous commands, and press Enter when I’ve found the one I want to run again. That’s one way to rerun a previously run command. Another, is to use the Get-History cmdlet. First, you use it to determine the ID of the command you want to rerun, such as we’ve done above. Then, you can run Get-History again with the -Id parameter and pipe that result to Invoke-History (aliases: ihy, r), such as we’ve done below. You can also just use Invoke-History -Id 1 without the use of Get-History.

PS> Get-History -Id 1 | Invoke-History
Get-Process | Select-Object -Last 2

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    263      11     1680       5132    46     0.02   3356 WUDFHost
    227      12     1824       6804    47     0.22   3840 WUDFHost

What about making a “menu,” where we can select the command we want to run and press OK to run it? Easy. If you’ve been paying attention to PowerShell, then you’ve probably seen the Out-GridView cmdlet before. Let’s put it to good use, and I’ll show you the cmdlets I lined up.

PS> Get-History | Out-GridView -PassThru | Invoke-History

When I enter the command above, it will show the dialog box below; however, your dialog box will be the default size. I’ve resized mine so that the image better fits on this page. If it’s important to you, you can change what the title says by including Out-GridView’s -Title parameter: Get-History | Out-GridView -PassThru -Title ‘History Menu’ | Invoke-History.

Invoke-a-Command-from-Get-History-Menu01

After I select an option from the list, and press OK, the history item will be rerun.

PS> Get-History | Out-GridView -PassThru | Invoke-History
Get-Process | Select-Object -Last 2

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    263      11     1680       5132    46     0.02   3356 WUDFHost
    227      12     1824       6804    47     0.22   3840 WUDFHost

PS>

Well, there it is. Something so simple and so obvious that I can’t believe I never thought of it before.