Viewing a VMware PowerCLI Get-AdvancedSetting Value When It Doesn’t Exist

The situation:  You want to ensure that an advanced setting is consistently applied through all of your VMs.

Getting Advanced Settings from all of your VMs… almost

You run this command in PowerCLI to find out which VMs are configured the way you want, and which need to be adjusted:

$AdvancedSettingName = 'isolation.tools.diskshrink.disable'
#(Could be anything, but this is the specific setting that I was working with)

Get-VM | Get-AdvancedSetting -Name $AdvancedSettingName

The output is simple enough, and for me the output was consistent – everything was “true”… but unfortunately, the results weren’t accurate because all of the VMs weren’t listed.

My problem was in assuming that it would always be either “true” or “false”, but it turns out that there is a third option:  “the advanced setting doesn’t exist for that object”.

That third option is the reason that I had an incomplete list. The output of Get-VM is passed as an input to Get-AdvancedSetting.  But the output of Get-AdvancedSetting is an advanced setting, not a VM. So if the VM doesn’t have that advanced setting at all, then it’s not included in the list.  That’s why I got an incomplete and inaccurate list of advanced settings:  only VMs with the advanced setting were included.

Is there a better way to list the advanced settings of a VM in PowerCLI

So one of the first things I noticed in the output is that it didn’t include the name of the VM in the output.

The easy way to get incomplete information from Get-AdvancedSetting

Even though the machine name isn’t included by default in the output of Get-AdvancedSetting, it’s there in the object.

$SettingName = 'isolation.tools.diskshrink.disable'
Get-VM | Get-AdvancedSetting -Name $SettingName | Select Entity, Name, Value

This gives you the information you asked for – the machine name along with the setting – but it still doesn’t include any VMs that don’t have the setting.

The slightly more complicated way that shows every VM whether it has the advanced setting or not

$Setting = 'isolation.tools.diskshrink.disable'


Get-VM | Select-Object Name, @{Name="DiskShrinkDisabled"; Expression={
  ($_ | Get-AdvancedSetting -Name $Setting).Value 
 } #end Expression
 } #end Hashtable

Or you could take it that last extra step and report if the value is TRUE, FALSE or if it doesn’t exist:

$AdvancedSetting = "isolation.tools.diskshrink.disable"
Get-VM | `
 Select Name, @{Name="DiskShrink"; Expression={
   #Start "Expression
   if ( ($_ | Get-AdvancedSetting -Name $AdvancedSetting).Value -eq "true") {
     "Disabled"
   } else { 
     #Start "Is it enabled or blank?"
     if ($_ | Get-AdvancedSetting -Name $AdvancedSetting) {
       "Enabled" 
     } 
     else { 
       "Setting Doesn't Exist"
     }
     #End "Is it enabled or blank?"
   }
   #End "Expression"
  }
  #End "DiskShrink Hashtable"
 } | Format-Table -Autosize

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.

A Simple PowerShell Script to Install SCOM Web Console Prerequisites

If you’re getting ready to install the System Center Operations Manager web console, there are several prerequisites that you need to install before you’re ready for it.

Luckily, if you’re comfortable with PowerShell this becomes SO easy, and ultimately repeatable.

Import-Module ServerManager 
Add-WindowsFeature NET-Framework-Core,Web-Static-Content,Web-Default-Doc,WebDir-Browsing,Web-Http-Errors,Web-Http-Logging,Web-Request-Monitor,Web-Filtering, Web-Stat-Compression,AS-Web-Support,Web-Metabase,Web-Asp-Net,Web-Windows-Auth –restart

See how easy this is? Thirteen feature installations, plus the time spent starting the UI, fumbling through it, and double- and triple-checking to make sure you’ve gotten every feature that you need; all of that time spent adds up and even adds risk that you’ll make a mistake, forcing you to do the same thing again, find which feature was missed and starting the installation again.

Enjoy this fast little script the next time you have to install a SCOM Web Console. As you can see, it really speeds you up to install the SCOM web console prerequisites with PowerShell.

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."

Get a Folder Size with PowerShell

It’s really easy to get the size of a folder with PowerShell. Even though the directory does not actually have a property for size, you just need to get the size of all of the files inside the folder.

function Get-FolderSize {
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
$Path,
[ValidateSet("KB","MB","GB")]
$Units = "MB"
)
  if ( (Test-Path $Path) -and (Get-Item $Path).PSIsContainer ) {
    $Measure = Get-ChildItem $Path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum
    $Sum = $Measure.Sum / "1$Units"
    [PSCustomObject]@{
      "Path" = $Path
      "Size($Units)" = $Sum
    }
  }
}

One of my favorite things about this script is the ability to choose to get the folder size in KB, MB or GB.

 

This won’t include any results from items that you don’t have read access to, so be aware of that. Otherwise, it’s a fast and easy way to get the folder size from PowerShell!

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.