How To Open A Directory Chooser Window With PowerShell

How to use a SAVE AS dialog window with PowerShell

I’m sure that you’ve seen plenty of scripts that set a path like “C:\temp” for logs, files, and other outputs.  But did you know that it’s actually very easy to add a pop up directory UI in PowerShell that gives a choice of where to save those output files? It’s true! And the amazing thing is that it really doesn’t take much time to write, it’s not even a lot of lines of code.

For this I’ll use any example.  Let’s use a script I shared previously on how to find which process is using the most CPU with PowerShell. First, to include this function in this script I will dot-source it from my calling script.

. C:\scripts\get-iLPHighCPUProcess.ps1

This doesn’t run the command, because I’m dot-sourcing it. This is a way of making the function in that second script available in this script that I’m writing now.

Next, we can call the function from our script, and save the output to any location:

Get-iLPHighCPUProcess | Out-File C:\temp\HighCPU_Processes.txt

But this will error out if there is no C:\temp folder created.  Yes, there are ways around that. You could use Test-Path to find out if the folder is there, and issue a warning if it isn’t. Or, you could force the creation of the file first by including something like this:

If (-Not (Test-Path C:\temp)) {New-Item -Itemtype directory -Path C:\temp -Force}

How to Add a “Save File As…” Dialog Window to Your PowerShell Scripts

Though both of those solutions are fine, they are NOT the point of this post!  This is about how to let the users choose where to save the file in PowerShell. So let’s take a look at that.

Here I will add one extra line to the same script. The purpose is to create the dialog that shows the user a directory window where they will choose their own location where they want the file saved. Even better, this let’s the user name their saved output with whatever file name they want.

$SaveChooser = New-Object -Typename System.Windows.Forms.SaveFileDialog
$SaveChooser.ShowDialog()

Look at that! The way to have a nice UI popup where the user can choose their own place to save a file is really easy. You can use the built-in .NET Framework object for working with files, folders and Windows Forms objects to get it done!

You can see it yourself by running just those two lines from the PowerShell console.  Notice that it has all of the functionality that you need. You can browse the folder structure, create or rename folders, and choose the file name to save to.

How to use a SAVE AS dialog window with PowerShell

When the “Save” button is pressed, the dialog is complete. After the dialog closes, control passes back to the PowerShell script. From there, you still have the SaveDialog object saved as a variable. But now, the variable has some important information stored in its properties!

To access the file and folder path where the file should be saved you can use the FileName property.

Get-iLPHighCPUProcess | Out-File $SaveChooser.Filename

How the Save File Dialog box looks in PowerShell

Put it all together, and the code looks like this:

. C:\Users\micha\OneDrive\scripts\Get-iLPHighCPUProcess.ps1
$SaveChooser = New-Object -TypeName System.Windows.Forms.SaveFileDialog
$SaveChooser.ShowDialog()
Get-iLPHighCPUProcess | Out-File $SaveChooser.Filename

That’s a whole lot of nice functionality for such a small amount of code, it’s definitely worth it to give some choice to the script operator. So use the SaveFileDialog object from .NET to improve your PowerShell scripts.

What is Your .NET Framework Version? Use PowerShell to Check

Finding out which .NET Framework Version is currently installed is not something that most people think of every day. However, if you are a  Windows Server Administrator managing web servers that run .NET web applications, then making sure that your servers are running the correct version can be very important!

Using PowerShell to Find the .NET Framework Version Installed on a Windows Computer

So how in the world can you check your server to see if it has the correct .NET Framework version installed? Well, the answer to that is PowerShell! Because the .NET Framework version that is installed on your computer is stored in the Windows Registry, this makes PowerShell the best option to retrieve the information.

But what about regedit?  The great thing about regedit is that it’s readily available, simple to use and presents the registry in a nice application. With that application, you can definitely do the same thing that we’re doing here with PowerShell. Unfortunately for regedit users, it really will take longer to navigate through the registry, find the right key, and then decipher the value to find out what version of .NET Framework is installed.

But with a PowerShell script, you can save it someplace and use it again later.  By doing this, you save yourself the trouble of having to look up the exact location in the registry that you have to find. Also, the registry doesn’t save the .NET Framework version as an easy to read description like “version 4.6.1”.  Instead, it stores the version information as the number of the release. So without the decoder ring, you’re just left trying to figure out what “378389” means.

Finding the .NET Framework Version on a Local System

This part is really simple.  You can copy and paste this code into a PowerShell ISE on the remote system, or save this as a .PS1 file and move it over to the server. Once the script is there, you can run it and it will output the .NET Framework version.

It does this by using a couple of very simple commands. First, it gets the registry key that holds the “release” value.

$NetRegKey = Get-Childitem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full'

Next, it gets the value of “Release” from the registry key.

$Release = $NetRegKey.GetValue("Release")

With the vale of the release saved into a variable, it’s easy to take an action based on what the value of that variable is. To do this, we will use a SWITCH statement. The action taken by the SWITCH statement will work like a translator to tell us what .NET Framework version is installed, based on the number in the release value.

Switch ($Release) {
   378389 {$NetFrameworkVersion = "4.5"}
   378675 {$NetFrameworkVersion = "4.5.1"}
   378758 {$NetFrameworkVersion = "4.5.1"}
   379893 {$NetFrameworkVersion = "4.5.2"}
   393295 {$NetFrameworkVersion = "4.6"}
   393297 {$NetFrameworkVersion = "4.6"}
   394254 {$NetFrameworkVersion = "4.6.1"}
   394271 {$NetFrameworkVersion = "4.6.1"}
   394802 {$NetFrameworkVersion = "4.6.2"}
   394806 {$NetFrameworkVersion = "4.6.2"}
   Default {$NetFrameworkVersion = "Net Framework 4.5 or later is not installed."}
}

Finally, we output the results by simply calling the $NetFrameworkVersion variable that was set using the switch.

$NetFrameworkVersion

Easy, right?

The Get-iLPNetFrameworkVersion script as a function

Here is the same commands wrapped up as a function that can be used to find the .NET Framework Version from one or more machines. If it is run without any parameters, then it runs against the local machine and does not use any PowerShell remoting features to operate. However, if you supply the “-Computer” parameter with one or more computers, then a remote PowerShell session is created, and the commands are run on the remote machines. Whether or not the script is run against just the local computer or a bunch of remote systems, the output includes both the computer name and the .NET Framework version.

function Get-iLPNetFrameworkVersion {
[CmdletBinding()]
param(
    [string[]]$Computer = "localhost"
)
$ScriptBlockToRun = {
    $NetRegKey = Get-Childitem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full'
    $Release = $NetRegKey.GetValue("Release")
    Switch ($Release) {
        378389 {$NetFrameworkVersion = "4.5"}
        378675 {$NetFrameworkVersion = "4.5.1"}
        378758 {$NetFrameworkVersion = "4.5.1"}
        379893 {$NetFrameworkVersion = "4.5.2"}
        393295 {$NetFrameworkVersion = "4.6"}
        393297 {$NetFrameworkVersion = "4.6"}
        394254 {$NetFrameworkVersion = "4.6.1"}
        394271 {$NetFrameworkVersion = "4.6.1"}
        394802 {$NetFrameworkVersion = "4.6.2"}
        394806 {$NetFrameworkVersion = "4.6.2"}
        Default {$NetFrameworkVersion = "Net Framework 4.5 or later is not installed."}
    }
    $Object = [PSCustomObject]@{
        Computername = $env:COMPUTERNAME
        NETFrameworkVersion = $NetFrameworkVersion
    }
    $Object
}
if ($Computer = "localhost") { 
         . $ScriptBlockToRun
} else {
        $Session = New-PSSession $Computer
        Invoke-Command -Session $RemoteSession -ScriptBlock $ScriptBlockToRun
}
}

Use XPath to Search XML Nodes by Node Text Value

Alright, another quick lesson in finding XML.

You see, an XML node like this:

<server name="DomainController1"></server>

Uses the name as an attribute. I just wrote a post about how to search XML by node attribute.

But that won’t do you a lick of good against the actual text value of the node.

<server>DomainController1</server>

Totally different beast. The trick here is to use the “text()” method.

$XML | Select-Xml -Xpath '//server[text()="DomainController1"]'

Oh yeah…  That’s the good stuff.

You can also search a few levels down. Here’s a sample from a Remote Desktop Connection Manager file.

<group>
   <properties>
      <expanded>True</expanded>
      <name>DomainControllers</name>
   </properties>
   <logonCredentials inherit="None">
      <profileName scope="File">myDomain\myAccount</profileName>
    </logonCredentials>
    <server>
       <properties>
           <name>DomainController1.myDomain.demo</name>
       </properties>
    </server>
</group>

If I want to get the GROUP node that has the SERVER named DomainController1.myDomain.demo in it… I can do that with XPath and Select-XML.

#This isn't exactly it, but it's a learning step to help you better understand.
$xml | Select-XML -XPath '//group/server/properties/name[text()="DomainController1.MyDomain.demo"]'

This is how to get the name node of the server that is in the group. See how close that is? Aren’t you already learning?

In English, that XPath statement translates to “Give me the NAME node that has the text value of DomainController1.MyDomain.demo, that is the childnode of a node called properties, which is a child of one called server, which is the child of a node called group. AND, that group node could be anywhere in the XML.”

We define outside of the brackets the node we want to receive. We define inside the brackets the evaluations to get there.

Since I want the GROUP node, I am going to stuff everything else inside the brackets.

$xml | Select-XML -XPath '//group[/server/properties/name/text()="DomainController1.Mydomain.demo"]'

Now that returns the group node.  You’re ready to clone the node, update attributes or insert a child node and then save the updated XML object back to the original XML file.

I recognize this might be a little confusing, but it’s a quirky little command. If you’re needing some extra help, leave a note in the comments and I’ll do my best to answer your questions.

Searching XML Nodes By Attribute Name with Select-XML

Any of you say this prayer after you’ve successfully remembered how to filter by the node attribute property with Select-XML and PowerShell?

Dear God. Please let me remember this syntax the next time I’m trying to search an XML file. If you will just spare me the hours of searchig through useless blog posts and references of XPath syntax I swear I’ll be good. I’ll be a better man to those around me; I’ll live like a boy scout forever.

I’ve said it, thought it and prayed it, but I guess God wanted me to just write it down, because I can never remember it when I want to. Luckily for me, I have this blog where I can jot down simple notes like this.

To search XML nodes with an attribute, use a freaking ‘@’ to indicate attribute name

#Any XML will do
[xml]$xml = Get-Content C:\windows\starter.xml

#Find all nodes of type "component"
$xml | Select-Xml -Xpath "//component"

#Find ONLY the nodes of type "component" with an attribute of "name"
$xml | Select-XML -XPath "//component[@name]"

#And ONLY the nodes of type "component" with an attribute "name" with a value of "Microsoft-Windows-themeservice"
$xml | Select-XML -XPath "//component[@name='Microsoft-Windows-themeservice]"

Hope this helps you. If you found this post, it’s going to be a real timesaver for working with XML.

Using the Add Method of a Hashtable Object

I saw an interesting approach to working with a hashtable the other day. It started with this:

$hashtable = @{}

This is different than how I usually create an array:

$array = @()

And also different from creating a PowerShell object:

$PSObject = [PSCustomObject]@{}

There are some real advantages to using a PSCustomObject instead of a hashtable. But if all you need to do is quickly and easily store a collection of name/key values, the “Add()” method of the hashtable object is pretty handy.

Here’s how it works.

$hashtable = @{}

$hashtable.Add("eyes","blue")
$hashtable.Add("name","Michael")

Compared to adding properties to a PSCustomObject with the Add-Member cmdlet, using the Add method is very appealing. It’s quick and to the point.

Just to be clear, I’m not advocating for the wide use of hashtables instead of PSCustomObjects. I think PSCustomObjects are the better structure, especially in a case where you’re creating multiple objects or later working with the objects to sort, select or output the data.

But in a case where you just want to very quickly and easily add some data into a table inside your script and then reference it later, then the Add method of the hashtable is super easy.

When working with most basic operations, working with the hashtable will be similar to working with properties on an object.

"My name is $($hashtable.name) and I have $($hashtable.eyes) eyes."

How to Check if a Server Needs a Reboot

If you’re trying to determine which of your servers require reboots, you’ll love this PowerShell script to check the status. It turns out that a simple way to identify servers that are pending reboot is to check the registry. This information is stored in the HKeyLocalMachine hive of the registry.

PowerShell is born and bred for working with the registry. Registry is one of the built-in PowerShell providers. There’s even already a PSDrive connected to that registry hive! You can browse around the registry just like you can the filesystem.

#Change to the registry drive. 
#Set-Location can also be invoked through its aliases - CD and SL
#Get-ChildItem can also be invoked through its aliases - Dir and LS

Set-Location HKLM:
Get-ChildItem

Wow! Super easy, right?

Now you just need to know where the “pending reboot” location is. There are a couple of places to check.

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired

Is where patches installed through automatic updates register the need to reboot.

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending

Is another place where pending reboots can be identified.

HKLM\SYSTEM\CurrentControlSet\Control\Session Manager

Is yet another. Finally, there is Configuration Manager which, if present, can be queried via WMI.

I found a function that I really like to check all four locations. I’ll need to wrap it up with some parameters to check remote computers, but in general it was a great start. I’ve adapted the function to return $true on the first condition that satisfies, since I only care about whether the computer is pending a reboot, and not where the source of the reboot is comping from.

 

#Adapted from https://gist.github.com/altrive/5329377
#Based on <http://gallery.technet.microsoft.com/scriptcenter/Get-PendingReboot-Query-bdb79542>
function Test-PendingReboot
{
 if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
 if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
 if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
 try { 
   $util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
   $status = $util.DetermineIfRebootPending()
   if(($status -ne $null) -and $status.RebootPending){
     return $true
   }
 }catch{}

 return $false
}

Find the processes using the most CPU on a computer with PowerShell

Michael Simmons - iLovePowerShell - Which Processes are Hogging the CPU
Michael Simmons - iLovePowerShell - Which Processes are Hogging the CPU
Photo: Michael Simmons, iLovePowerShell.com

Have a complaint from a user that a server is sluggish? Maybe you’re just curious if the problem you’re seeing on a server is related to a process consuming a lot of CPU, or you know the CPU is pegged and you want to identify which process is the culprit.

I had a similar situation, and so I wrapped it up into a nice little package that I’ll share with you here. This uses WMI to find processes running high CPU with PowerShell.

It can run in one of two ways – by reporting all processes using more than a certain percentage of the CPU or by reporting a the top CPU hogging processes. I use parameter sets for those different options, so it’s one or the other. I considered making it possible to list “top 3 processes consuming more than 10%,” which could have been done very easily, but decided I preferred it this way.

 

Function Get-iLPHighCPUProcess {
<#
.SYNOPSIS
    Retrieve processes that are utilizing the CPU on local or remote systems.

.DESCRIPTION
    Uses WMI to retrieve process information from remote or local machines. You can specify to return X number of the top CPU consuming processes
    or to return all processes using more than a certain percentage of the CPU.

.EXAMPLE
    Get-HighCPUProcess

    Returns the 3 highest CPU consuming processes on the local system.

.EXAMPLE
    Get-HighCPUProcess -Count 1 -Computername AppServer01

    Returns the 1 highest CPU consuming processes on the remote system AppServer01.


.EXAMPLE
    Get-HighCPUProcess -MinPercent 15 -Computername "WebServer15","WebServer16"

    Returns all processes that are consuming more that 15% of the CPU process on the hosts webserver15 and webserver160
#>

[Cmdletbinding(DefaultParameterSetName="ByCount")]
Param(
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias("PSComputername")]
    [string[]]$Computername = "localhost",
    
    [Parameter(ParameterSetName="ByPercent")]
    [ValidateRange(1,100)]
    [int]$MinPercent,

    [Parameter(ParameterSetName="ByCount")]
    [int]$Count = 3
)


Process {
    Foreach ($computer in $Computername){
    
        Write-Verbose "Retrieving processes from $computer"
        $wmiProcs = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process -Filter "idProcess != 0" -ComputerName $Computername
    
        if ($PSCmdlet.ParameterSetName -eq "ByCount") {
            $wmiObjects = $wmiProcs | Sort PercentProcessorTime -Descending | Select -First $Count
        } elseif ($psCmdlet.ParameterSetName -eq "ByPercent") {
            $wmiObjects = $wmiProcs | Where {$_.PercentProcessorTime -ge $MinPercent} 
        } #end IF

        $wmiObjects | Foreach {
            $outObject = [PSCustomObject]@{
                Computername = $computer
                ProcessName = $_.name
                Percent = $_.PercentProcessorTime
                ID = $_.idProcess
            }
            $outObject
        } #End ForeachObject
    } #End ForeachComputer
}

}

Things I like about this function are:

  • Concise output: Computername, ProcessName, Percent (of CPU), and ID (Process ID)
  • Accepts pipeline input by property value of “ComputerName” or “PSComputerName”
  • Easy to use against multiple computers
  • Flexible: I like the option to find the 3 highest CPU processes or all processes above 5 percent (or any percent)
  • Accepts multiple computers through parameter (as an array) or through the pipeline

Questions? Comments? Whether it’s a different approach or just saying “Thanks” I really appreciate the feedback.

Using -match and the $matches variable in PowerShell

iLovePowerShell Logo

iLovePowerShell LogoI’ve been rereading the Windows PowerShell Cookbook and I came across a variable I hadn’t noticed before…

It turns out to be related to the -match comparison operator. -Match performs a regular expression comparison. A simple way of thinking about regular expressions is that they “describe” the patterns of characters. Another way of thinking of regular expressions is “Wildcards on steroids.”

For these examples, I’m going to keep things very simple as far as regular expressions go:

.   <– means “anything”
*  <– means “zero or more of it”
so
.* <– means “zero or more of anything”

<#####################
 -Match and $Matches 
#####################> 

<# Like -eq, -match will evaluate True/False 
   when compared against one object #>
"One" -eq "One"
"One" -match ".*"

<# Also like -eq, -match passes objects that evaluate to 
  TRUE through the pipeline when compared against multiple objects#>
"One","Two","Three" -eq "Two"
"One","Two","Three" -match "T.*"

An interesting thing about using the -match comparison operator is that when matches are evaluated, the result is stored in a unique variable, $matches.

The $matches variable takes a little getting used to.

#1 - $Matches doesn't work with -eq
"One", "Two", "Three" -eq "One" | Foreach {$Matches[0]}
<# Surprised? If you've dropped these 
  examples into the ISE, it looks like it works. #>

#2 - $Matches really doesn't work with -eq
"One", "Two", "Three" -eq "Two" | Foreach {$Matches[0]}
<# See? I tricked you! The reason it looked like it worked on #1 was because
   it was still set from the last time -match was used. #>

#3 - $Matches isn't set when evaluating multiple objects 
"One" -match ".*"
"One", "Two", "Three" -match "T.*"
$Matches

#4 - $Matches CAN be used with multiple items, but evaluate each item individually
"One" -match ".*"
"One","Two","Three" | Where-Object {$_ -match "T.*"} | Foreach {$Matches[0]}
$Matches
<#Notice how $matches now equals "Three"? What happened to "Two"? 
  Since I used the pipeline to break the list into three items, 
  the -match comparison in the Where cmdlet does update the 
  $Matches variable. But immediately after evaluating TRUE for "Two", 
  it updates with "Three" and Two returns to the ether. #>

#5 - Don't be fooled by trying to -match on multiples
"One","Two","Three" -match "T.*" | Foreach {$Matches[0]}
<#See? It's tricky.#>

Finally, you’ll just want to notice that I keep switching between $Matches and $Matches[0]. $Matches is a variable that contains a hashtable. The hashtable (a key-pair) has a key and a value. The key is the index and the value is “what was returned as the match.” Since there is only one value in the $Matches variable, you can get to the value of the match by referencing the key-pair by its name.

Alright, so you’ve seen how $Matches works. But why use it?

In the examples that I’ve shown here, you probably aren’t that impressed. I promise I kept it simple so it wouldn’t be too intimidating with the crazy looking regular expressions. Regex can look really nuts.

Let’s use another example that is equally as useless but at least serves as a better example of why we might like $Matches.

#Let's make a little list of words
$words = "The quick brown fox jumped over the lazy dog".Split()

#Now show only words that start with a letter between "a" and "l" and also have an "o" somewhere in the word.
$Words | Where {$_ -match "[A-L,a-l].*o.*"} | Foreach {$Matches[0]}

That really can’t be done with using a -like comparison operator and normal wildcards. Still not sure if you’d have a use case for having the matches of a regular expression displayed? Here’s when you want to use it.

Grab a list of strings. Pipe them into a Where-Object or Select-String to perform a -match against them. If what you want is the whole string when it has the match, then you just use the standard output. But if what you want is only the resulting match, then you can pipe it to Foreach-Object {$matches[0]}

Did this help? Have something to add? Leave a comment below.

Easiest way to Shuffle an Array with PowerShell

I was going nuts trying to sort an array into a random order. I was hoping for something in PowerShell that would be along the lines of Shuffle() in PHP or Ruby.Shuffle an Array in PowerShell (The Easy Way!)

In fact, after looking at it I was a little surprised at all of the things that an array can’t do in PowerShell.

But that’s ok – it turns out to be really easy and if you’re thinking that you need to learn a differentlanguage to shuffle an array in PowerShell you’re dead wrong.

It’s super easy to randomize the order of an array in PowerShell – you just have to simplify and think about it in a different way.

#Give me a list - any list will do. Here's 26 numbers.
$MyList = 0..25

#Shuffle your array content but keep them in the same array

$MyList = $MyList | Sort-Object {Get-Random}

#Randomize the contents of your array and save them into a new array

$MyShuffledList = $MyList | Sort-Object {Get-Random}

This is so easy it defies belief. Why does it work?

Since the Sort-Object can accept pipeline input, it’s often used to organize the contents of a list based on one property or another. You’ve probably used it for sorting by names, or for sorting files by their size. But a magical thing happens when you tell it to not sort by a specific property but instead pass it a scriptblock.

For each object that is passed into Sort-Object through the pipeline the scriptblock is called. And since Get-Random evaluates to a rather large random number – the objects in the array are sorted based on all of those random integers.

I know this was an eye-opener for me, and I really like doing it this way. Before coming across the sort method I tried going through each item in the array and adding a noteproperty to it.  Which worked since I was using an array of objects anyway but was way clunkier than doing it this way.

Leave any questions or feedback in the comments and do me a favor and hit the like button on my facebook page for me, ok? Thanks!