Hyper-V and PowerShell: A Deep Dive into Get-VM

Table of contents

In the introductory article of this series, we talked about objects in PowerShell and how the large and grandiose uses of PowerShell are difficult to explain. While reflecting on that article, I realized that out of all the programming languages that I know, PowerShell’s verb-noun convention is probably the best way there is. This article will revisit that subject along with an investigation of the Get-VM cmdlet. If you’ve already got a good understanding of PowerShell objects and just want to learn about Get-VM, skip to the second half.

PowerShell Objects

The concept of objects in PowerShell is extremely important, as they are what sets it apart from almost every other interactive command-line system. In comparison, let’s examine a typical Windows command-line: a directory listing:

CMD DirectoryNow, let’s look at the same thing in PowerShell:

PowerShell Directory ListingThey look mostly the same, don’t they? Well, it turns out that they’re quite a bit different. The first way that they’re different is that “dir’ doesn’t even really exist in PowerShell. Try this: Get-Alias dir:

dir AliasIn PowerShell, “dir” is just an alias for Get-ChildItem (as a side note, so is “gci’, and for you Linux types, “ls”). So, the fact that “dir” in PowerShell actually calls on a cmdlet is the first difference. That’s probably the most minor difference.

In the Windows command line, the directory listing is just a text dump. What you see is all there is. If you want to do anything with the returned information, such as work further with one of the directories, you have to design a scraper. I’ve done it; it’s not pleasant. But, in PowerShell, you don’t have to do anything like that at all. It looks like a text dump in the above example because we didn’t instruct PowerShell to do anything else with Get-ChildItem’s results. You can do a lot more in PowerShell, if necessary.

If the World Ran on PowerShell

To illustrate, we’ll go through a real-world activity as though PowerShell were your only method of interaction.

Let’s say that lately, you’ve been feeling like you just haven’t had enough furry sociopaths in your life. So, to address that shortcoming, you decide to get a cat. Upon entering the pet store, you tell the clerk: “Get-Cat”. In a standard command-line world, doing something like this would just get you a list of cats. In PowerShell, you’d have the option to get actual cats, although if you just put it like that, with no directives or modifiers or pipes, you wind up with the same list of cats. That’s sort of like shopping in a catalog though. I personally wouldn’t recommend ever buying a cat, but if you’re convinced that your carpet is overly lacking in that special feline ammonia smell, you should always get to know any potential pet before selecting it.

Right away, we get to do something different in PowerShell than we could do in any plain old command line. Instead of just telling the clerk to get all the cats for you, you could say, “Get-Cat | Test-Cat”. What this little gem does is have the clerk retrieve all the cats for you, one at a time, and allow you to try them out. Instead of just having a little list like the old command line would have given you, you’ve now got a chance to determine for yourself which one is least likely to try to kill you in your sleep.

The problem with such a thing is that we’re still probably just going to get a list. The “Test-Cat” function might discard any objects that don’t fit some criteria, but in the usage we’ve seen so far, it still has no directive for what to do with whatever it finds, so it’s just going to dump it to the screen. So, if all you’ve done is issued “Get-Cat | Test-Cat”, then you’re probably just looking at a list of cats that passed the test, or perhaps a list of all the cats with a “Pass” or “Fail” indicator, or whatever the function is designed to do. For purposes of illustration, we’ll assume that “Test-Cat” passes through every object that passes the test and silently discards those that fail. Since we’ve gone through all the cats and found the one that we want, the next step is to purchase the demon incarnate animal.

So, how do you suppose we do that? Did you guess “Get-Cat | Test-Cat | Purchase-Cat”? Seems logical, doesn’t it? But no, that’s not how it would work. When we pipe things, they go to functions. Functions should either add, remove, modify, or delete the piped objects or they should add, remove, modify, or delete other objects based on the piped objects. In this case, we don’t want to do any of that. We want to store the objects that the functions retrieve. So, what we’ll tell the clerk is “$MyShoppingCart = Get-Cat | Test-Cat”. Yes, it’s going to make the clerk get out all those cats one at a time again, and they’re all going to have to go through that testing process again. Fortunately, our clerk is PowerShell, and PowerShell doesn’t get fussy at having to redo the same work a lot.

Now you’ve got an object named $MyShoppingCart. And in that $MyShoppingCart, you’ve got at least one cat object. This is where we’re really far away from what a plain command line can do. This isn’t a list of cats or a batch of cat pictures. These are real cats. They .Scratch() and .Bite() and .Poo(InYourShoe). As for the $MyShoppingCart object, your daughter can ask if “$MyShoppingCart.Contains(ThatCuteBlackAndWhiteOne)” and you can respond, “$true”. If she wants to try them out as well, you can easily run “$DaughterFavorites = $MyShoppingCart | Test-Cat -Mode Daughter” and each one will be examined, one at a time, in a test modified to her definition, and kept in a group that you named DaugherFavorites. From here, you can probably figure out what to do, if you really want a cat. Me, I’m going to go back to doing Windows things.

More with Directories

Now we return to our earlier directory listing with a newfound understanding. We can start with

dir | fl *

You’ll remember the “fl *” from the first post in this series as “Format-List” with all properties. If you run this against a directory with more than a few items in it, you’ll get a pretty long list. Let’s shorten it a bit:

dir flWhat’s happened here is that we’ve been shown all of the properties for the first object in the directory. That’s something that you’d need multiple functions for in the traditional command line. You’ll also remember that you can trim the list down. For instance, you might just want to see the attributes for the objects:

dir Attributes TableThe thing you want to always keep in mind is that Format-List (“fl”) and Format-Table (“ft”) are only for formatting screen output. They are intended for usage at the point at which you don’t need the objects to be objects anymore. What comes out of these is just for looking at. You don’t want to do anything else with them and you’ll have difficulties if you try.

If you want to work with the returned list as a group of objects, that’s easily done:

$DirList = dir

Now you’ve stored the contents of the current directory inside a variable named DirList. Unlike the output-only routines shown above, you now have an actual object collection. To see that you have an object, run this:

$DirList[0] | Get-Member

The [0] refers to the first object in $DirList because all collections in PowerShell, and a great many languages, start counting at 0. What you see from Get-Member isn’t really important for this discussion except that you understand you’re looking at an object, not just a plain text dump. This is an object that can be operated on, for instance by passing it a function that accepts this type of object as input. Remove-Item would be an example of such a function.


Now that you have an idea of the object nature of PowerShell, let’s move along to Get-VM. As you probably expect, this retrieves a virtual machine object. Let’s look at the syntax for this function from Get-Help:

    Get-VM [[-Name] <String[]>] [-ComputerName <String[]>] [<CommonParameters>]

    Get-VM [-ClusterObject] <PSObject> [<CommonParameters>]

    Get-VM [[-Id] <Guid>] [-ComputerName <String[]>] [<CommonParameters>]

There are three Get-VM lines of syntax, so there are three different parameter sets. What that means is that if you can’t mix parameters from one set with parameters in another. You can’t give Get-VM a virtual machine’s Name and Id at the same time, for instance.

Since all of the parameters in all the parameter sets have brackets around them, they’re all optional. You can run Get-VM without any parameters and it will choose its default parameter set using defaults. -Full shows what the defaults for all parameters are. Get-VM run with nothing specified gets all VMs on the local host.

There are two positional parameters for this function. The first is -Name. You can tell that it’s positional because its name is surrounded by brackets. The second positional parameter is Id. So, if you run Get-VM with any string, it will assume that you gave it the name of a virtual machine:

Get-VM svdc1

will be treated exactly the same as:

Get-VM -Name svdc1

The object that it retrieves can be passed to any function that accepts virtual machine objects, such as Start-VM or Remove-VM. It can be stored in a variable so you can perform multiple activities. To trigger the second positional parameter, you would provide Get-VM with a System.GUID object.

What we promised for this article was a deep dive, and what we promised for this series was to show you things that can’t be done as easily with Hyper-V Manager. Let’s say that I want to see the current memory demand for both of my domain controllers. They live on separate hosts, so even though I can use Hyper-V Manager, I can’t see them together. I also can’t see how much is actually assigned to these guests. With PowerShell, this is no problem:

Get-VM svdc* -ComputerName svhv1, svhv2 | ft Name, MemoryDemand, MemoryAssigned -AutoSize

On my system, that outputs:

Name  MemoryDemand MemoryAssigned
----  ------------ --------------
svdc1    713031680      849346560
svdc2    852492288     1027604480

So right there are two basic reasons you’d turn to PowerShell over the graphical tools available to you: first, you can retrieve information from multiple VMs across multiple hosts in one shot, and second, you can look at properties that the tools don’t show. You don’t have to guess at what’s available. Just use “fl *” on any VM and you can see all available properties:

VMName                      : svdc1
VMId                        : e3a95f29-2102-4aed-ac59-ce5651afa3a1
Id                          : e3a95f29-2102-4aed-ac59-ce5651afa3a1
Name                        : svdc1
State                       : Running
IntegrationServicesState    : Up to date
OperationalStatus           : {Ok}
PrimaryOperationalStatus    : Ok
SecondaryOperationalStatus  :
StatusDescriptions          : {Operating normally}
PrimaryStatusDescription    : Operating normally
SecondaryStatusDescription  :
Status                      : Operating normally
Heartbeat                   : OkApplicationsHealthy
ReplicationState            : Disabled
ReplicationHealth           : NotApplicable
ReplicationMode             : None
CPUUsage                    : 0
MemoryAssigned              : 849346560
MemoryDemand                : 713031680
MemoryStatus                : OK
SmartPagingFileInUse        : False
Uptime                      : 1.02:00:50
IntegrationServicesVersion  : 6.3.9600.16384
ResourceMeteringEnabled     : False
ConfigurationLocation       : C:LocalVMs
SnapshotFileLocation        : C:LocalVMs
AutomaticStartAction        : Start
AutomaticStopAction         : ShutDown
AutomaticStartDelay         : 0
SmartPagingFilePath         : C:LocalVMs
NumaAligned                 : False
NumaNodesCount              : 1
NumaSocketCount             : 1
Key                         : Microsoft.HyperV.PowerShell.VirtualMachineObjectKey
IsDeleted                   : False
ComputerName                : svhv1
Version                     : 5.0
Notes                       :
Generation                  : 2
Path                        : C:LocalVMs
CreationTime                : 8/24/2014 6:44:12 PM
IsClustered                 : False
SizeOfSystemFiles           : 60258
ParentSnapshotId            :
ParentSnapshotName          :
MemoryStartup               : 536870912
DynamicMemoryEnabled        : True
MemoryMinimum               : 536870912
MemoryMaximum               : 2147483648
ProcessorCount              : 2
RemoteFxAdapter             :
NetworkAdapters             : {Network Adapter}
FibreChannelHostBusAdapters : {}
ComPort1                    : Microsoft.HyperV.PowerShell.VMComPort
ComPort2                    : Microsoft.HyperV.PowerShell.VMComPort
FloppyDrive                 :
DVDDrives                   : {DVD Drive on SCSI controller number 0 at location 1}
HardDrives                  : {Hard Drive on SCSI controller number 0 at location 0}
VMIntegrationService        : {Time Synchronization, Heartbeat, Key-Value Pair Exchange, Shutdown...}

You can just pick most any of the properties that you want to see and use them with fl or ft. One property I find especially useful is VMId, as this is how Hyper-V thinks of the virtual machine. You can use that to locate the virtual machine’s configuration files and determine how it is tracked in the cluster.

The properties that you can’t so easily pull are with the items in curly braces. Those are multi-value properties. You have to use a special parameter with “select” (which is an alias for “Select-Object”) called “ExpandProperty” (or just “Expand”):

PS C:> Get-VM svdc1 | select -ExpandProperty VMIntegrationService

VMName Name                    Enabled PrimaryStatusDescription SecondaryStatusDescription
------ ----                    ------- ------------------------ --------------------------
svdc1  Time Synchronization    True    OK
svdc1  Heartbeat               True    OK                       OK
svdc1  Key-Value Pair Exchange True    OK
svdc1  Shutdown                True    OK
svdc1  VSS                     True    OK
svdc1  Guest Service Interface False   OK

The real power of Get-VM is that it gets a virtual machine object, which, as we mentioned before, can be used with any function that accepts a virtual machine object as input. If these virtual machine objects are handled one a time, there’s not a huge advantage over the GUI, although there are some neat tricks. For instance, let’s say you didn’t assign enough virtual CPUs to your company’s primary application server. You can’t take it down in the middle of the day, and you’d rather not stay up all night. With PowerShell, you have a simple solution. Just craft a .PS1 file with the following:

Get-VM svbigapp | Stop-VM -Force -Passthru | Set-VM -ProcessorCount 4 -Passthru | Start-VM

Then, you can use the built-in Scheduled Tasks applet to set it to run at some point during the night (don’t forget to change your execution policy to something lower than AllSigned — I like RemoteSigned, but to each her/his own).

Those with some experience will know that the above line could actually have started with the Stop-VM function. However, this article is about Get-VM, not Stop-VM, and the point of that line is to show you just how easily a virtual machine object can be passed from one function to another, right on down the line until we don’t need it anymore. But, knowing about Stop-VM doesn’t suddenly make Get-VM useless, either. Get-VM still has powers that Stop-VM doesn’t.

What you can do with Get-VM that you can’t do anywhere else — at least, not to the same degree — is selectively retrieve virtual machines. You saw earlier that I used “svdc*” to retrieve both of my domain controller VMs. That works because they match on their first four characters and none of my other virtual machines fit that particular string. A basic wildcard string like that will work with the -Name parameter of all the built-in -VM functions. For anything more complicated than that, you need Get-VM.

In truth, what you need is Where-Object. In many scripts posted online, you’ll see this as its alias “where”. When you’re typing it at the prompt, just use a question mark instead: ?. This handy little function combined with Get-VM will allow you to select VMs by just about any combination you can think of.

For example, let’s say my web/database system is under some sort of attack and the security office has asked me to just shut them all off while they check for intrusion. These VMs are spread across three hosts and have non-contiguous names. No problem for PowerShell:

Get-VM -ComputerName svhv1, svhv2, svhv3, svhv4 | ? { $_.Name -like "svbackendsql*" -or $_.Name -like "svfrontendweb*" -or $_.Name -like "svbiztier*" } | Stop-VM -TurnOff -Force

The line reads fairly easily, but if you’re new to this then you should take a few minutes to examine the elements. The Get-VM part is retrieving every single virtual machine object from every single listed host. That’s usually not a problem, but it’s something to think about if you craft a query in which hundreds of objects might be retrieved. This behavior is also why you need Get-VM; if you tried such a thing with Stop-VM, then all the virtual machines would be stopped before Where-Object ever got a look.

Each of the matching items are then passed to the Where-Object function (alias of ?, remember) which checks each of them in turn to see if it matches against the conditions set forth inside the curly braces. Those braces are very important; Where-Object may not work as you expect without them, depending on the comparison that you use. Their presence indicates that you are providing Where-Object with a ScriptBlock object. If you check Get-Help for Where-Object, you’ll see that there’s a positional parameter called FilterScript that will accept it. That ScriptBlock is where the magic happens. I’ve shown you a simplistic matching pattern. In addition to -like (which allows for wildcards), you also have -notlike, -eq (equals, as in “exact match”, no wildcards), -ne (not equals), and, for some real power, -match (regular expressions).

That’s just the beginning, though. See the $_ items? That basically means, “this object”. Since Where-Object is operating on the items one at a time, that specifically means “this particular VM object”. So $_.Name means “the name of this virtual machine”. That means that you’re not really just working with a name. You’re working with an entire virtual machine. You could just as easily use something like:

Get-VM | ? { $_.Name -like "*exchange*" -and $_.DynamicMemoryEnabled }

If you named your Exchange guests with “exchange” anywhere in the string, this will show you if any of them have Dynamic Memory enabled. You can even design complex script blocks if you want. The way that Where-Object works on them is very simple: any time the script block evaluates to True, the object being tested is passed along to the next function in the pipeline. Any time it evaluates to False, it just goes to the next item to be evaluated. You can use this fact to test your script blocks before passing them into functions that will modify the objects.

$TestVM = Get-VM svdc1
$Gen2DomainControllersWithoutSecureBootScriptBlock = {
	if($_.Name -match "dcd$" -and $_.Generation -eq 2)
		(Get-VMFirmware -VM $_).SecureBoot -eq "Off"
$TestVM | ? $Gen2DomainControllersWithoutSecureBootScriptBlock

The first line is where we establish the test virtual machine as “svdc1”. The second line begins the definition of our test script block, which is intended to find domain controller virtual machines that are Generation 2 and don’t have Secure Boot enabled. That requires two separate tests, so we need a complex script block. Line 3 does the first match: it reads “if this virtual machine has a name that matches the letters “dc” followed by a numeral at the end of the name and the virtual machine is generation 2″. If both are true, then it goes to the second matching test on line 5. That line runs Get-VMFirmware on the VM and retrieves its “SecureBoot” property. If Secure Boot is “Off”, then this line evaluates to True; if not, then this line evaluates to False. Since there’s nothing else after this in the script block that will process, then this True or False is how the entire script block will be handled by Where-Object. The $false on line 9 is what happens if the virtual machine’s name doesn’t match the “dcd$” check or if it isn’t Generation 2. Since it’s the last thing in its execution path, that’s also how Where-Object will interpret the script block in that condition. The very final line of the script is where the test is performed. You can go back and change $TestVM a few times to see if the script block is working as intended. If it is, then you can design something that will address whatever condition you’re working on. In this case, we might build something that shuts matching VMs down, enables Secure Boot, and turns them all back on.

Using Get-VM and Where-Object, you can perform very elaborate processes that simply cannot be accomplished in any other tool, and you can do so fairly easily — with a little practice.

In the next post in the series we will show you an easy way to retrieve a Virtual Machine’s IP Address.

Altaro Hyper-V Backup
Share this post

Not a DOJO Member yet?

Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!

20 thoughts on "Hyper-V and PowerShell: A Deep Dive into Get-VM"

Leave a comment or ask a question

Your email address will not be published. Required fields are marked *

Your email address will not be published.

Notify me of follow-up replies via email

Yes, I would like to receive new blog posts by email

What is the color of grass?

Please note: If you’re not already a member on the Dojo Forums you will create a new account and receive an activation email.