Altaro PowerShell Hyper V Cookbook
Altaro PowerShell Hyper V Cookbook
PowerShell
Hyper-V
Cookbook
Table of contents
Introduction
Using a Template
12
20
20
24
24
Memory Usage
29
34
37
41
42
45
49
57
60
Additional Resources
61
62
About Altaro
62
Share it!
Introduction
For most Windows administrators, the Hyper-V management console is more than sufficient.
However you are limited to what the console is designed to handle. For more complex tasks,
or those that might extend beyond the scope of the management console, you need an
alternative. That alternative is Windows PowerShell.
With PowerShell, if you can work with one item, such as a virtual machine, VHD or snapshot,
you can work with 10, 100 or 1000 with practically very little extra effort. This e-book will
include a number of recipes for managing Hyper-V using Windows PowerShell. Some of the
recipes are one or two line commands. Others, are more complicated scripts. You dont have
to be a PowerShell expert to use the recipes, although certainly the greater your PowerShell
experience, the more you will be able to take this recipes and customize them to meet your
requirements. All of the recipes are included in an accompanying ZIP file. Many of these
recipes were originally published on the Altaro Hyper-V blog (http://www.altaro.com/hyper-v/)
but I have revised and/or made some minor adjustments to many of the scripts.
DOWNLOAD
Share it!
Figure 1
An alternative is to install the Remote Server Administration Tools and specify the Hyper-V
role. Just remember to include the Hyper-V PowerShell module. http://www.microsoft.com/enus/search/DownloadResults.aspx?q=remote%20server%20administration%20tools
I also recommend that your Hyper-V servers have PowerShell remoting enabled. The Hyper-V
commands dont require it, but there will be some recipes where it is more efficient to run the
command through a PSSession.
Many of the recipes consist of a PowerShell function that resides in a PowerShell script. In
order to execute the function, you must first have a script execution policy that will allow you
to run scripts. Then you will need to dot source the script file into your PowerShell session.
PS C:\> . .\scripts\New-VMFromTemplate.ps1
Now you can invoke any functions defined inside the script.
Please take note that none of these scripts or functions require anything other than
the Hyper-V module, unless noted. These commands have not been tested in an
environment with System Center Virtual Machine Manager, which has similar command
names to the Hyper-V PowerShell module.
Share it!
Finally, and Id like to think it goes without saying, but you should test all of these recipes in
a non-production environment. Neither I nor Altaro Software make any claims, warranties
or guarantees about how these recipes will perform or behave in your environment. This is
definitely use at your own risk.
Many of the cmdlets can be used in pairs such as Get-VM and Start-VM. For example, you can
get a set of virtual machines that meet some criteria and then start them.
Get-VM chi* | where {$_.state -eq Off} | Start-VM -AsJob
This command gets all virtual machines that start with CHI and that are not running and then
starts them. The startup process runs as a background job so you get your PowerShell prompt
back immediately. The Hyper-V cmdlets are using the local computer. Most of the Hyper-V
cmdlets let you specify a remote Hyper-V server using the Computername parameter.
Get-VM chi* -ComputerName chi-hvr2 | where {$_.state -eq Off} | Start-VM -asjob
This is the exact same command except it will use virtual machines running on the server CHIHVR2. The recipes throughout this book will use many of the Hyper-V cmdlets in the Hyper-V
module. Sometimes as simple one-line commands and other times in more complex scripts or
functions. Dont forget that to use any of the Hyper-V cmdlets, you must be running PowerShell
in an elevated session as an administrator. If you feel that your PowerShell skills could use a
little improving, I have some resources at the end of the book.
Lets start cooking.
Share it!
Using a Template
Often you may have virtual machines that fall into different categories. The first recipe is a
function that will create a virtual machine based on a pre-defined type of Small, Medium or
Large. You will have to edit the script if you would like to change any of the settings.
New-VMFromTemplate.ps1
#requires -version 3.0
Function New-VMFromTemplate {
<#
.Synopsis
Provision a new Hyper-V virtual machine based on a template
.Description
This script will create a new Hyper-V virtual machine based on a template or hardware
profile. You can create a Small, Medium or Large virtual machine. You can specify the virtual
switch and paths for the virtual machine and VHDX files.
All virtual machines will be created with dynamic VHDX files and dynamic memory. The virtual
machine will mount the specified ISO file so that you can start the virtual machine and load
an operating system.
VM Types
Small (default)
MemoryStartup=512MB
VHDSize=10GB
ProcCount=1
MemoryMinimum=512MB
MemoryMaximum=1GB
Medium
MemoryStartup=512MB
VHDSize=20GB
ProcCount=2
MemoryMinimum=512MB
MemoryMaximum=2GB
Large
MemoryStartup=1GB
VHDSize=40GB
ProcCount=4
MemoryMinimum=512MB
MemoryMaximum=4GB
This script requires the Hyper-V 3.0 PowerShell module.
.Parameter Path
The path for the virtual machine.
.Parameter VHDRoot
The folder for the VHDX file.
.Parameter ISO
The path to an install ISO file.
.Parameter VMSwitch
The name of the Hyper-V switch to connect the virtual machine to.
.Parameter Computername
The name of the Hyper-V server. If you specify a remote server, the command will attempt
to make a remote PSSession and use that. Any paths will be relative to the remote computer.
Parameter Start
Share it!
Share it!
Share it!
Share it!
Try {
Write-Verbose Configuring new virtual machine $name
Write-Verbose ($setParam | out-string)
$VM | Set-VM @setparam
}
Catch {
Write-Warning Failed to configure virtual machine $Name
Write-Warning $_.Exception.Message
#bail out
Return
}
If ($Start) {
Write-Verbose Starting the virtual machine
Start-VM -VM $VM
}
} #if $VM
} #if local
else {
Write-Verbose Running Remotely
#create a PSSession
Try {
$sess = New-PSSession -ComputerName $Computername -ErrorAction Stop
Write-Verbose copy the function to the remote session
$thisFunction = ${function:New-VMFromTemplate}
Invoke-Command -ScriptBlock {
Param($content)
New-Item -Path Function:New-VMFromTemplate -Value $using:thisfunction -Force |
Out-Null
} -Session $sess -ArgumentList $thisFunction
Write-Verbose invoke the function with these parameters
Write-Verbose ($PSBoundParameters | Out-String)
Invoke-Command -ScriptBlock {
Param([hashtable]$Params)
New-VMFromTemplate @params
} -session $sess -ArgumentList $PSBoundParameters
} #Try
Catch {
Write-Warning Failed to create a remote PSSession to $computername. $($_.Exception.
message)
}
#remove the PSSession
Write-Verbose Removing pssession
$sess | Remove-PSSession -WhatIf:$False
}
Write-Verbose Ending command
} #end function
This command can be run locally or you can specify a computer name. If you specify a remote
computer name, the command will create a temporary PSSession and invoke necessary
commands remotely. The function will use the default location for the new VHDX file and
Share it!
10
virtual machine unless you specify alternate locations. Optionally, you can also specify an ISO
file. This file will be loaded into the virtual machines DVD drive which will be configured
to boot from the media. The end result is that when you start the virtual machine, the setup
process will kick off immediately. Be aware that any paths are relative to the computer.
Heres the command in action. Because this is the first time using the function, I need to dot
source it.
PS C:\> . C:\scripts\New-VMFromTemplate.ps1
The function has complete help like any other cmdlet, including examples. Here is how I
created a small virtual machine on a remote server that also automatically mounted the
Windows Server 2012 ISO file that is on the remote Hyper-V server. After the virtual machine is
created it is automatically started.
PS C:\> New-VMFromTemplate -Name Web03 -VMType Small -ISO d:\iso\windows_server_2012_
r2_x64.iso -computername chi-hvr2 -verbose -start
Like many of the recipes, this command has verbose output if you want to trace the command
as you can see in Figure 2.
Figure 2
Share it!
11
:
:
:
:
:
G:\iso\en_windows_server_2012_x64_dvd_915478.iso
Windows Server 2012 SERVERSTANDARDCORE
Windows Server 2012 SERVERSTANDARDCORE
1
6862
ISOPath
Name
Description
Index
SizeMB
:
:
:
:
:
G:\iso\en_windows_server_2012_x64_dvd_915478.iso
Windows Server 2012 SERVERSTANDARD
Windows Server 2012 SERVERSTANDARD
2
11444
ISOPath
Name
Description
Index
SizeMB
:
:
:
:
:
G:\iso\en_windows_server_2012_x64_dvd_915478.iso
Windows Server 2012 SERVERDATACENTERCORE
Windows Server 2012 SERVERDATACENTERCORE
3
6844
ISOPath
Name
Description
Index
SizeMB
:
:
:
:
:
G:\iso\en_windows_server_2012_x64_dvd_915478.iso
Windows Server 2012 SERVERDATACENTER
Windows Server 2012 SERVERDATACENTER
4
11440
Generally, the only part of the image name you need is what is in upper case.
.Notes
Last Updated:
Version
: 1.0
.Link
Get-WindowsImage
#>
Share it!
12
[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory,HelpMessage=Enter the path to the ISO file.,
ValueFromPipeline,ValueFromPipelineByPropertyName)]
[ValidateScript({ Test-Path -path $_})]
[Alias(FullName)]
[string]$Path
)
Process {
Write-Verbose Mounting $path as read-only
$iso = Mount-DiskImage -ImagePath $path -Access ReadOnly -PassThru -StorageType ISO
$drive = {0}:\ -f ($iso | Get-DiskImage | Get-Volume).DriveLetter
$wimPath = Join-Path -Path $drive -ChildPath sources\install.wim
Write-Verbose Reading image information from $wimPath
#add the ISO path to the output and make sure to sort by index.
#The image size is also formatted in MB.
Get-WindowsImage -ImagePath $wimPath |
Add-Member -MemberType NoteProperty -Name ISOPath -Value $path -PassThru |
Select ISOPath,@{Name=Name;Expression={$_.ImageName}},
@{Name=Description;Expression={$_.ImageDescription}},
@{Name=Index;Expression={$_.ImageIndex}},
@{Name=SizeMB;Expression={$_.ImageSize /1MB -as [int]}} | Sort Index
Write-Verbose Dismounting disk image
$iso | Dismount-DiskImage
} #end process
} #end function
In the help example you can see how to use this command. Next, you will need to download
a script from Microsoft called Convert-WindowsImage.ps1. The most recent version
supports Windows 8 and later (http://gallery.technet.microsoft.com/scriptcenter/ConvertWindowsImageps1-0fe23a8f). My script will call this script to apply a Windows image from an
ISO to a new VHD or VHDX file.
New-VMFromISO.ps1
#requires -version 3.0
<#
.Synopsis
Create a Hyper-V virtual machine from an ISO file.
.Description
This script This script requires the Convert-WindowsImage.ps1 script which you can download
from Microsoft:
http://gallery.technet.microsoft.com/scriptcenter/Convert-WindowsImageps1-0fe23a8f
Share it!
13
The default location for the script is C:\Scripts or edit this script file accordingly.
The script will create a virtual memory with disk, memory and processor specifications. The
VHDX file will be created and Convert-WindowsImage will apply the specified Windows image
from the ISO. You can use the -ShowUI parameter for a GUI to create the VHDX file, select
the ISO and apply the image.
.Parameter Name
The name of your new virtual machine.
.Parameter Path
The path to store your new virtual machine. The default is the server default location.
.Parameter ISOPath
The name and path to the ISO file.
.Parameter DiskName
The name of your new virtual disk. Include the VHD or VHDX extension.
.Parameter DiskPath
The folder for the new virtual disk. The default is the server default location for disks.
.Parameter Size
The size of the new virtual disk file.
.Parameter Memory
The amount of memory for the new virtual machine.
.Parameter Switch
The name of the virtual switch for the new virtual machine.
.Parameter ProcessorCount
The number of processors for the new virtual machine.
.Parameter Edition
The name of the Windows image from the ISO.
.Parameter Unattend
The filename and path of an unattend.xml file to be inserted into new virtual disk.
.Parameter ShowUI
Run the Convert script using the ShowUI parameter. You will be prompted to re-enter the
path you specified for the new virtual disk. You might also need to manually remove the
virtual drive that is created. DO NOT use this parameter if running this script in a remote
PSSession.
.Example
PS C:\> $iso = G:\iso\en_windows_server_2008_r2_standard_enterprise_datacenter_and_web_
x64_dvd_x15-59754.iso
PS C:\> $newParams=@{
Name = 2008Web
DiskName = WebDemo-01.vhdx
ISOPath = $Iso
Edition = ServerWeb
Size = 10GB
Memory = 1GB
Switch = Work Network
ProcessorCount = 2
}
PS C:\> c:\scripts\new-vmfromiso.ps1 @newparams
This example creates a hashtable of parameters to splat to the script which will create a
new virtual machine running the Web edition of Windows Server 2008.
.Example
PS C:\> c:\scripts\new-vmfromiso.ps1 -name DemoVM -ShowUI
This command will launch the script and create a virtual machine called DemoVM using all
Share it!
14
Share it!
15
[int64]$Size = 20GB,
[Parameter(ParameterSetName=Manual)]
[ValidateScript({Test-Path $_ })]
[string]$Unattend,
[ValidateScript({$_ -ge 256MB})]
[int64]$Memory = 1GB,
[ValidateNotNullorEmpty()]
#set your default switch
[string]$Switch = Work Network,
[ValidateScript({$_ -ge 1})]
[int]$ProcessorCount = 2,
[Parameter(ParameterSetName=UI)]
[switch]$ShowUI
)
#!!! DEFINE THE PATH TO THE CONVERT-WINDOWSIMAGE.PS1 SCRIPT !!!
$convert = c:\scripts\Convert-WindowsImage.ps1
if (-Not (Test-Path -Path $convert)) {
Write-Warning Failed to find $convert which is required.
Write-Warning Please download from:
Write-Warning http://gallery.technet.microsoft.com/scriptcenter/Convert-WindowsImageps10fe23a8f
Write-Warning and try again.
#bail out
Return
}
#region creating the VHD or VHDX file
if ($pscmdlet.ParameterSetName -eq UI) {
if ($pscmdlet.ShouldProcess(ShowUI)) {
&$convert -showUI
Write-Warning You may need to manually use Dismount-DiskImage to remove the mounted ISO
file.
$ok= $False
do {
[string]$vhdPath = Read-Host `nWhat is the complete name and path of the virtual disk
you just created? Press enter without anything to abort
if ($vhdPath -notmatch \w+) {
Write-warning No path specified. Exiting.
Return
}
if (Test-Path -Path $vhdPath) {
$ok = $True
}
else {
Share it!
16
Write-Host Failed to verify that path. Please try again. -ForegroundColor Yellow
}
} Until ($ok)
} #should process
}
else {
#manually create the VHD
#region Convert ISO to VHD
Write-Verbose Converting ISO to VHD(X)
$vhdPath = Join-path -Path $DiskPath -ChildPath $DiskName
#parse the format from the VHDPath parameter value
[regex]$rx = \.VHD$|\.VHDX$
#regex pattern is case-sensitive
if ($vhdpath.ToUpper() -match $rx) {
#get the match and strip off the period
$Format = $rx.Match($vhdpath.toUpper()).Value.Replace(.,)
}
else {
Throw The extension for VHDPath is invalid. It must be .VHD or .VHDX
#Bail out
Return
}
#define a hashtable of parameters and values for the Convert-WindowsImage
$convertParams = @{
SourcePath = $ISOPath
SizeBytes = $size
Edition = $Edition
VHDFormat = $Format
VHDPath = $VHDPath
ErrorAction = Stop
}
if ($Unattend) {
$convertParams.Add(UnattendPath,$Unattend)
}
Write-Verbose ($convertParams | Out-String)
#define a variable with information to be displayed in WhatIf messages
$Should = VM $Name from $ISOPath to $VHDPath
#region -Whatif processing
If ($pscmdlet.ShouldProcess($Should)) {
Try {
#call the convert script splatting the parameter hashtable
&$convert @convertParams
}
Catch {
Write-Warning Failed to convert $ISOPath to $VHDPath
Share it!
17
Write-Warning $_.Exception.Message
}
} #should process
#endregion
#endregion
}
#endregion
#region Creating the virtual machine
Write-Verbose Creating virtual machine $Name
Write-Verbose VHDPath = $VHDPath
Write-Verbose MemoryStartup = $Memory
Write-Verbose Switch = $Switch
Write-Verbose ProcessorCount = $ProcessorCount
Write-Verbose Path = $Path
#new vm parameters
$newParam = @{
Path = $Path
Name = $Name
VHDPath = $VHDPath
MemoryStartupBytes = $Memory
SwitchName = $Switch
}
New-VM @NewParam | Set-VM -DynamicMemory -ProcessorCount $ProcessorCount -Passthru
#dismount the disk image if still mounted
if ($ISOPath -AND (Get-DiskImage -ImagePath $ISOPath).Attached) {
Write-Verbose dismounting $isopath
Dismount-DiskImage -ImagePath $ISOPath
}
Write-Verbose New VM from ISO complete
#endregion
My script assumes you put the Convert-WindowsImage script in C:\Scripts. Otherwise, you will
need to change this line:
$convert = c:\scripts\Convert-WindowsImage.ps1
Share it!
18
This is a script, not a function, so you need to specify the full name to run it. In this example
I am creating locally a new virtual machine called Dev01 with a new virtual disk that will be
created in D:\DevDisks\Dev01_C.vhdx from the SERVERSTANDARDCORE image on the
specified ISO. This may take about 5 minutes to complete, but when finished the virtual
machine is ready to go. By the way, my script has a default setting for the virtual switch. I
recommend modifying the script to set a new default or remember to specify a switch when
running the command.
As an alternative, you Convert-WindowsImage.ps1 script has a ShowUI parameter. I have
added that to my script so that you can run a command like this:
PS C:\> C:\scripts\New-VMfromISO.ps1 -Name Test01 ShowUI
You will get a form which makes it easier to browse for the ISO, select the Windows edition
and create the virtual disk.
Figure 3
Share it!
19
The working directory is where I want to create the VHDX file. When you create the name, be
sure to include the extension that matches your format type. Otherwise, everything else is
essentially the same.
This script doesnt have a provision to connect to a remote server. However, if you copy the
necessary scripts to the remote server, you can invoke it with PowerShell remoting from a client.
PS C:\> invoke-command {c:\scripts\new-vmfromiso.ps1 -name Dev02 -diskname Dev02_C.vhdx
-isopath d:\iso\windows2012-x64.iso -edition serverstandardcore memory 4GB} -computername
chi-hvr2
The remote server must have the Storage and DISM modules which if it is running Windows
Server 2012 or later should not be an issue. Of course, another option is to create the virtual
machine locally on a Windows 8 client with Hyper-V enabled, export the virtual machine and
then import it on the server.
Virtual Machines
The easiest way to see your virtual machines from PowerShell is to run Get-VM. From a client
you can specify the name of a Hyper-V host.
PS C:\> get-vm -ComputerName chi-hvr2
Figure 4
Share it!
20
Unfortunately, the Get-VM cmdlet doesnt have any filtering parameters. So if you wanted to
see only running virtual machines you would need a command like this.
PS C:\> get-vm -ComputerName chi-hvr2 | where { $_.state -eq running}
Figure 5
To simplify things, here is a function that combines these steps into a single command.
Get-MyVM.ps1
#requires -version 3.0
#requires -module Hyper-V
Function Get-MyVM {
<#
.Synopsis
Get VM by state
.Description
This command is a proxy function for Get-VM. The parameters are identical to that command
with the addition of a parameter to filter virtual machines by their state. The default is
to only show running virtual machines. Use * to see all virtual machines.
.Example
PS C:\> get-myvm -computername chi-hvr2
Name
---CHI-CORE01
CHI-DC04
CHI-FP02
CHI-Win81
State
----Running
Running
Running
Running
CPUUsage(%)
----------0
0
0
0
MemoryAssigned(M)
----------------512
1024
512
1226
Uptime
-----2.01:47:42
2.01:48:10
2.01:47:40
2.01:47:11
Status
-----Operating
Operating
Operating
Operating
normally
normally
normally
normally
Share it!
21
CHI-Win8 Saved 0
Position=0,
[Parameter(ParameterSetName=Id)]
[Parameter(ParameterSetName=Name)]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:computername,
[Microsoft.HyperV.PowerShell.VMState]$State=Running
)
begin
{
Write-Verbose Getting virtual machines on $($computername.ToUpper()) with a state of
$state
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue(OutBuffer, [ref]$outBuffer))
{
$PSBoundParameters[OutBuffer] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand(Get-VM, [System.
Management.Automation.CommandTypes]::Cmdlet)
#remove my custom parameter because Get-VM wont recognize it.
$PSBoundParameters.Remove(State) | Out-Null
$scriptCmd = {& $wrappedCmd @PSBoundParameters | Where {$_.state -like $state} }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
Share it!
22
throw
}
}
process
{
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
} #end function
Without getting too deep into the technical details, this function is a proxy function based on
Get-VM. Ive inserted a Where-Object command so that you can run it like this:
PS C:\> get-myvm -ComputerName chi-hvr2
Name
State
----
-----
Status
------
CHI-CORE01 Running 0
512
CHI-DC04
Running 0
1024
CHI-FP02
Running 0
512
CHI-Win81
Running 0
1226
The default is to display running virtual machines. But you can specify different states.
PS C:\> get-myvm -State saved computername chi-hvr2 | select Name
Name
---CHI-FP01
CHI-Win8
Use a value of * for the State parameter to get all virtual machines, or use the original GetVM.
Share it!
23
Name
----
Status
------
Dev02 Off
Web02 Off
Web03 Off
These are the virtual machines on CHI-HVR2 that have been created in the last 7 days. There
is one caveat with this technique: it appears that virtual machines that have been imported
will have a CreationTime of 12/31/1600 7:00:00 PM, or something very similar depending on
your time zone. But anything created with the management console, the New-VM cmdlet, and
presumably the associated APIs should have a valid CreationTime value.
Figure 6
Share it!
24
If for some reason the file doesnt exist you will get an error. Getting an error about a missing
file on one hand is useful, but it makes it a bit more difficult to get a feel for the big picture.
Also, from my testing I believe that when you use Get-VHD remotely, it translates the path to
use the hidden administrative shares. That could be problematic as many admins are disabling
those shares and it is likely not to be very cloud friendly. So here is my solutions for you.
Get-VHDInfo.ps1
#requires -version 4.0
#requires -modules Hyper-V
#requires -RunAsAdministrator
Function Get-VHDInfo {
<#
.Synopsis
Get virtual disk information.
.Description
This command will get virtual disk information for a given set of virtual machines on a
Hyper-V server. The default is all virtual machines.
The command uses PowerShell remoting to verify that the virtual disk file exists.
.Example
PS C:\> Get-VHDInfo -VMName chi-fp02 -Computername chi-hvr2
Getting disk file information on chi-hvr2 for virtual machine chi-fp02
VM
: CHI-FP02
Path
: C:\VM\CHI-FP02\Virtual Hard Disks\CHI-FP02_C.vhdx
VhdFormat
: VHDX
VhdType
: Dynamic
Size
: 21474836480
FileSize
: 20069744640
FragmentationPercentage : 5
ParentPath
:
Attached
: True
Verified
: True
ComputerName
: CHI-HVR2
VM
: CHI-FP02
Path
: C:\vhd\chi-fp02-disk2.vhdx
VhdFormat
: VHDX
VhdType
: Dynamic
Size
: 10737418240
FileSize
: 306184192
FragmentationPercentage : 7
ParentPath
:
Attached
: True
Verified
: True
ComputerName
: CHI-HVR2
.Example
PS C:\> Get-VHDInfo -computername chi-hvr2 | export-csv c:\work\DiskReport.csv -notype
Share it!
25
Get virtual disk information for all VMs on server CHI-HVR2 and export to a CSV file.
.Example
PS C:\scripts> Get-VHDInfo -Computername chi-hvr2 | sort FragmentationPercentage -Descending
| select -first 3 -Property VM,Path,Frag*,*size
Getting disk file information on chi-hvr2 for virtual machines *
VM
Path
FragmentationPercentage
Size
FileSize
: Dev02
: C:\Users\Public\Documents\Hyper-V\Virtual Hard Disks\Dev02_C.vhdx
: 33
: 21474836480
: 5641338880
VM
Path
FragmentationPercentage
Size
FileSize
: CHI-FP02
: C:\vhd\chi-fp02-disk2.vhdx
: 27
: 10737418240
: 306184192
VM
Path
FragmentationPercentage
Size
FileSize
: CHI-FP02
: C:\VM\CHI-FP02\Virtual Hard Disks\CHI-FP02_C.vhdx
: 15
: 21474836480
: 20069744640
Share it!
26
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[alias(Name)]
[string[]]$VMName=*,
[ValidateNotNullorEmpty()]
[string]$Computername = $env:computername
)
Write-Host Getting disk file information on $computername for virtual machines $VMName
-ForegroundColor Cyan
Try {
$disks = Get-VM -name $VMname -computername $computername -ErrorAction Stop |
Select-Object -ExpandProperty harddrives | Select-Object VMName,Path,Computername
}
Catch {
Throw $_
}
#continue if there are some disks
if ($disks) {
#create a temporary PSSession to the remote computer so we can test the path
Try {
if ($computername -ne $env:computername) {
Write-Verbose Creating a temporary PSSession top $computername
$sess = New-Pssession -ComputerName $Computername -ErrorAction Stop
}
}
Catch {
#failed to create PSSession
Throw $_
#bail out
Return
}
Write-Verbose Processing disks...
foreach ($disk in $disks) {
Write-Verbose (VM {0} : {1} -f $disk.VMName,$disk.path)
Try {
$disk | Get-VHD -ComputerName $computername -ErrorAction Stop |
Select-Object -property @{Name=VM;Expression={$disk.vmname}},
Path,VHDFormat,VHDType,Size,FileSize,FragmentationPercentage,ParentPath,Attached,
@{Name=Verified;Expression={
if ($computername -eq $env:computername) {
Test-Path -path $_.path
}
else {
$diskpath = $_.path
Invoke-command -ScriptBlock {Test-Path -path $using:diskpath} -session $sess
}
}},Computername
} #Try
Catch {
Share it!
27
The script wraps up the one line command I showed earlier and adds some code to use TestPath to verify if the file exists. If you are querying a remote computer, the function will create a
temporary PSSession. You can use it like this:
PS C:\> get-vhdinfo chi* -Computername chi-hvr2 | out-gridview -title Chicago VMs
This example gets all of the virtual machines that start with chi on the server CHI-HVR2 and
sends the results to Out-Gridview (figure 7).
Figure 7
Share it!
28
VM
: Dev01
Path
: D:\VHD\Dev01_C.vhdx
VHDFormat
: VHDX
VHDType
: UNKNOWN
Size
: 0
FileSize
: 0
FragmentationPercentage :
ParentPath
Attached
: False
Verified
: False
Computername
: win81-ent-01
I intentionally renamed one of the disk files on a Windows 8.1 box to test. Because PowerShell
is writing an object to the pipeline, you can do just about anything you want with it. Here is a
one-line command to get the total file size in GB for all virtual disks.
PS C:\> get-vhdinfo -computername chi-hvr2
| measure Filesize -sum | Format-Table
Count,@{Name=SizeGB;Expression={$_.Sum/1gb}} AutoSize
SizeGB
-----
------
14 85.4192204475403
Memory Usage
Another useful metric to monitor is memory. The Hyper-V module includes a cmdlet that will
display memory information.
PS C:\> Get-VMMemory -VMName CHI-FP02 -ComputerName chi-hvr2
VMName
DynamicMemoryEnabled Minimum(M) Startup(M) Maximum(M)
------------------------- ---------- ---------- ---------CHI-FP02 True
512
512
2048
Share it!
29
As with most things in PowerShell, there is more to this object that what you see on the screen.
PS C:\> Get-VMMemory -VMName CHI-FP02 -ComputerName chi-hvr2 | Select *
Startup
DynamicMemoryEnabled
Minimum
Maximum
Buffer
Priority
MaximumPerNumaNode
ResourcePoolName
ComputerName
Name
Id
:
:
:
:
:
:
:
:
:
:
:
IsDeleted
VMId
VMName
VMSnapshotId
VMSnapshotName
Key
:
:
:
:
:
:
536870912
True
536870912
2147483648
20
50
5952
Primordial
chi-hvr2
Memory
Microsoft:5FE62AF7-CE0A-477C-8DB4-E133CBC31C8F\4764334de001-4176-82ee-5594ec9b530e
False
5fe62af7-ce0a-477c-8db4-e133cbc31c8f
CHI-FP02
00000000-0000-0000-0000-000000000000
:
:
:
:
:
:
CHI-HVR2
CHI-DC04
True
1024
849
1024
Share it!
30
Minimum
Maximum
Buffer
Priority
:
:
:
:
1024
2048
20
50
Share it!
31
ParameterSetName=VM)]
[ValidateNotNullorEmpty()]
[Microsoft.HyperV.PowerShell.VirtualMachine[]]$VM,
[ValidateNotNullorEmpty()]
[Parameter(ValueFromPipelinebyPropertyName)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:COMPUTERNAME
)
Begin {
Write-Verbose Starting $($MyInvocation.Mycommand)
} #begin
Process {
if ($PSCmdlet.ParameterSetName -eq Name) {
Try {
$VMs = Get-VM -name $VMName -ComputerName $computername -ErrorAction Stop
}
Catch {
Write-Warning Failed to find VM $vmname on $computername
#bail out
Return
}
}
else {
$VMs = $VM
}
foreach ($V in $VMs) {
#get memory values
Try {
Write-Verbose Querying memory for $($v.name) on $($computername.ToUpper())
$memorysettings = Get-VMMemory -VMName $v.name
-ComputerName $Computername
-ErrorAction Stop
if ($MemorySettings) {
#all values are in MB
$hash=[ordered]@{
Computername = $v.ComputerName.ToUpper()
Name = $V.Name
Dynamic = $V.DynamicMemoryEnabled
Assigned = $V.MemoryAssigned/1MB
Demand = $V.MemoryDemand/1MB
Startup = $V.MemoryStartup/1MB
Minimum = $V.MemoryMinimum/1MB
Maximum = $V.MemoryMaximum/1MB
Buffer = $memorysettings.buffer
Priority = $memorysettings.priority
}
#write the new object to the pipeline
New-Object -TypeName PSObject -Property $hash
} #if $memorySettings found
} #Try
Catch {
Share it!
32
Throw $_
} #Catch
} #foreach $v in $VMs
} #process
End {
Write-Verbose Ending $($MyInvocation.Mycommand)
} #end
} #end Get-VMMemoryReport
To use you can specify either the name or names of a virtual machine or pipe the results of a
Get-VM comma
PS C:\scripts> Get-VMMemoryReport chi-dc04 -Computername chi-hvr2
Computername
Name
Dynamic
Assigned
Demand
Startup
Minimum
Maximum
Buffer
Priority
:
:
:
:
:
:
:
:
:
:
CHI-HVR2
CHI-DC04
True
1024
849
1024
1024
2048
20
50
All of the memory values are formatted as MB. Heres another example that takes the output
and creates an HTML report:
PS C:\> get-vm -computer chi-hvr2 | Where { $_.state -eq running }| get-vmmemoryreport
| Sort Maximum | convertto-html -title VM Memory Report -css c:\scripts\blue.
css -PreContent <H2>Hyper-V Memory Report</H2> -PostContent <i>report created by
$env:username</i> | out-file c:\work\vmmemreport.htm
This command is getting all of the running virtual machines on CHI-HVR2, getting memory
information, sorting on the Maximum size and then creating an HTML report which you can
see in Figure 8.
Figure 8
Share it!
33
LastUse
------4/8/2014 12:17:03 PM
6/17/2014 1:32:52 PM
6/23/2014 2:03:41 PM
6/23/2014 2:04:03 PM
6/23/2014 2:04:28 PM
6/23/2014 2:04:30 PM
LastUseAge
---------76.01:47:30.2752590
6.00:31:40.7218754
00:00:52.3668297
00:00:30.2643841
00:00:04.9566880
00:00:03.0382539
Computername
-----------chi-hvr2
chi-hvr2
chi-hvr2
chi-hvr2
chi-hvr2
chi-hvr2
Get last use information for any virtual machine starting wit CHI and sort by LastUseAge
in descending order.
.Example
PS C:\> get-vm -computer chi-hvr2 | where {$_.state -eq off} | get-vmlastuse
VMName
-----CHI-Client02
CHI-DCTEST
Dev02
LastUse
------4/8/2014 12:17:03 PM
6/17/2014 1:32:52 PM
6/19/2014 2:04:18 PM
LastUseAge
---------76.01:47:50.6724602
6.00:32:01.7385359
4.00:00:36.5491423
Share it!
Computername
-----------chi-hvr2
chi-hvr2
chi-hvr2
34
Web01
Web02
Web03
6/17/2014 2:05:27 PM
6/17/2014 9:28:46 PM
6/17/2014 9:28:44 PM
5.23:59:28.6523976
5.16:36:10.5266729
5.16:36:12.6050236
chi-hvr2
chi-hvr2
chi-hvr2
Get last use information for any virtual machine that is currently off on server CHIHVR2.
.Notes
Last Updated: June 23, 2014
Version
: 2.0
.Inputs
String or Hyper-V Virtual Machine
.Outputs
custom object
.Link
Get-VM
#>
[cmdletbinding()]
Param (
[Parameter(Position=0,
HelpMessage=Enter a Hyper-V virtual machine name,
ValueFromPipeline,ValueFromPipelinebyPropertyName)]
[ValidateNotNullorEmpty()]
[alias(vm)]
[object]$Name=*,
[Parameter(ValueFromPipelineByPropertyname)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:COMPUTERNAME
)
Begin {
Write-Verbose -Message Starting $($MyInvocation.Mycommand)
} #begin
Process {
if ($name -is [string]) {
Write-Verbose -Message Getting virtual machine(s)
Try {
$vms = Get-VM -Name $name -ComputerName $computername -ErrorAction Stop
}
Catch {
Write-Warning Failed to find a VM or VMs with a name like $name on
$($computername.ToUpper())
#bail out
Return
}
}
else {
#otherwise well assume $Name is a virtual machine object
Write-Verbose Found one or more virtual machines matching the name
$vms = $name
}
if ($vms) {
Share it!
35
Share it!
36
}
#clean up any PSSessions
Remove-PSSession -Name $vm.computername -ErrorAction SilentlyContinue
} #process
End {
Write-Verbose -Message Ending $($MyInvocation.Mycommand)
} #end
} #end function
The function selects the first virtual disk file associated with a virtual machine, using the
assumption that this disk holds the operating system and is likely to change when the virtual
machine is powered on. You can use it like this:
PS C:\> get-vmlastuse -computer chi-hvr2
LastUse
LastUseAge
Computername
------
-------
----------
------------
CHI-Client02
4/8/2014 12:17:03 PM
76.01:49:37.8312351
chi-hvr2
CHI-DCTEST
6/17/2014 1:32:52 PM
6.00:33:47.7675333
chi-hvr2
If I had wanted, I could have removed any virtual machine starting with CHI on server CHIHVR2 that hasnt been used in more than 75 days.
Share it!
37
Get-VMOS.ps1
#requires -version 3.0
Function Get-VMOS {
<#
.Synopsis
Get the installed Windows operating system on a virtual machine.
.Description
This command will display the installed Windows operating system on a Hyper-V virtual
machine. The virtual machine must be running.
The function uses WMI to query remote computers.
.Example
PS C:\> Get-VMOS CHI-core01 -Computername chi-hvr2
VMName
-----CHI-CORE01
OperatingSystem
Computername
-------------------------Windows Server 2012 R2 Datacenter CHI-HVR2
OperatingSystem
--------------Windows Server 2012 R2 Datacenter
Windows Server 2012 Datacenter
Windows Server 2012 R2 Standard
Windows 8.1 Pro
Computername
-----------CHI-HVR2
CHI-HVR2
CHI-HVR2
CHI-HVR2
Process {
Share it!
38
Share it!
39
}
else {
Write-Warning Failed to find virtual machine $VMName
}
} #Process
End {
Write-Verbose -Message Ending $($MyInvocation.Mycommand)
} #end
} #end function
This function relies on Get-WMIObject which means you must have WMI access to any remote
computer, which you probably already do. The default behavior is to return information for all
virtual machines on the local host, but you can limit your query to a single virtual machine:
PS C:\> get-vmos chi-dc04 -computername chi-hvr2
VMName
OperatingSystem
Computername
------
---------------
------------
CHI-DC04
CHI-HVR2
OperatingSystem
Computername
------
---------------
------------
CHI-Win81
CHI-HVR2
CHI-DCTEST
CHI-HVR2
CHI-FP02
CHI-Client02
CHI-HVR2
CHI-HVR2
CHI-DC04
CHI-HVR2
CHI-CORE01
Notice that some virtual machines have no operating system, because the VM is not running.
Youll want to do something like this:
PS C:\> get-vm chi* -computername chi-hvr2 | where {$_.state -eq running} | get-vmos |
format-table -auto
VMName
OperatingSystem
Computername
------
---------------
------------
CHI-Win81
CHI-HVR2
CHI-FP02
CHI-HVR2
CHI-DC04
CHI-HVR2
Share it!
40
Figure 9
Figure 10
Share it!
41
LastWriteTime
Length Name
------------------ ---6/26/2013
8:51 PM 9399435264 Win8.1PreviewBase.vhdx
Getting unused virtual disk files on server CHI-HVR2 in the default location.
.Example
PS C:\> get-obsoletevhd -computer chi-hvr2 -Path c:\vhd
Directory: C:\vhd
Mode
----a---
LastWriteTime
Length Name
------------------ ---5/9/2014
1:59 PM 10842275840 Essentials.vhdx
SizeGB
-----82.3568634986877
This example is finding all unused virtual disk files in G:\VHDS on SERVER01 and then
calculating how much disk space they are consuming.
.Notes
Last Updated: June 25, 2014
Version
: 1.0
Share it!
42
.Link
Get-VHD
#>
[cmdletbinding()]
Param(
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
#use the value for -Computername is specified, otherwise the local computer
[string]$Path=(Get-VMHost -computername ( &{if ($computername) { $computername} else {
$env:computername}})).VirtualHardDiskPath,
[Alias(CN)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:computername
)
Write-Verbose -Message Starting $($MyInvocation.Mycommand)
Write-Verbose Searching for obsolete virtual disk files in $Path on $($Computername.
ToUpper())
#initialize an array to hold file information
$files = @()
Try {
#get currently used virtual disk files
Get-VM -computername $computername -ErrorAction Stop | Select -ExpandProperty
HardDrives |
Get-VHD -ComputerName $computername |
foreach {
$files+=$_.path
if ($_.parentPath) {
$files+=$_.parentPath
} #if path
} #foreach
} #try
Catch {
Throw $_
#bail out
}
if ($files) {
#filter out duplicates
$diskfiles = $files | Sort | Get-Unique -AsString
write-verbose Attached files
$diskfiles | Write-Verbose
write-verbose Orphaned files in $path
$sb = {
Param($path)
if (Test-Path -path $Path) {
dir -Path $path -file -filter *.vhd? -Recurse
}
else {
Write-Warning Failed to find path $path on $($env:computername)
}
}
Share it!
43
This function, by default, will search the local computer using the default location for virtual
disk files. When you query a remote computer, the function will use a temporary PSSession
with Invoke-Command in order to get a directory listing on the remote computer. You may be
wondering why I dont use Get-VHD to identify obsolete or unused files. It is true that GetVHD results will show an Attached property, but that will be false if the virtual machine is not
running, so that isnt a valid property to use in this situation.
PS C:\> get-obsoletevhd computer server01
Directory: D:\VHD
Mode
----a---a---a---a---a---a---a---a---a---
LastWriteTime
Length Name
------------------ ---4/27/2014
2:19 PM 102760448 Demo2.vhdx
6/17/2014 12:37 PM
4194304 small2_C.vhdx
6/17/2014
1:23 PM
4194304 small3_C.vhdx
6/17/2014
1:28 PM
4194304 small4_C.vhdx
6/17/2014
1:29 PM
4194304 small5_C.vhdx
6/17/2014 12:32 PM
4194304 small_C.vhdx
4/9/2014
8:22 AM
4194304 temp.vhdx
5/3/2014 12:54 PM 5362417664 test2.vhdx
6/18/2014
4:29 PM 5574230016 xDev01_C.vhdx
This example shows the unused virtual disk files in the default location on Hyper-V server
SERVER01. Remember, the output you see is on the remote server. If you wanted to delete
these files, you could use commands like this:
PS C:\> $old = get-obsoletevhd -computer server01
This saves the results to a variable. Then, use Invoke-Command to delete them remotely.
Share it!
44
Figure 11
You might also want to know how much space the snapshot files are consuming. This will
require PowerShell remoting to query a remote server, but really isnt that much more difficult.
Share it!
45
Invoke-Command {
Get-VMSnapshot -VMName * |Select -ExpandProperty HardDrives | Get-Item |
Measure-Object -Property Length -sum |
Select Count,@{Name=SizeGB;Expression={$_.Sum/1GB}}
} -ComputerName chi-hvr2 | Select * -ExcludeProperty runspaceId
In this command, Im creating a custom property called SizeGB that takes the sum of all the
associated disk files and converts the value to GB, which you can see in Figure 12.
Figure 12
This is a quick script but you could easily turn it into a function or expand upon it. Figure 13
depicts the script in action.
Share it!
46
Figure 13
If you look at help examples for Remove-VMSnapshot, youll see an example for removing old
snapshots. I took that idea and expanded it into a more full-fledged Powershell function.
Remove-OldVMSnapshot.ps1
#requires -version 3.0
#requires -module Hyper-V
Function Remove-OldVMSnapshot {
<#
.Synopsis
Remove old Hyper-V snapshots.
.Description
This command will find and remove snapshots older than a given number of days, the default
is 90, on a Hyper-V server. You can limit the removal process to specific virtual machines
as well as specific types of VM snapshots.
This command will remove all child snapshots as well so use with caution. The command
supports -Whatif and -Confirm.
.Example
PS C:\> Remove-OldVMSnapshot -VMName Ubunto-Demo -computername SERVER01
This command removed all snapshots for the Ubunto-Demo virtual machine on SERVER01 that
is older than 90 days.
.Example
PS C:\> Remove-OldVMSnapshot -computername chi-hvr2 -days 14 -whatif
What if: Remove-VMSnapshot will remove snapshot Profile Cleanup Test.
These are the snapshots older than 14 days on server CHI-HVR2 that would be removed.
.Example
PS C:\> Remove-OldVMSnapshot -computer chi-hvr2 -days 14 -confirm
Share it!
47
Confirm
Are you sure you want to perform this action?
Remove-VMSnapshot will remove snapshot Profile Cleanup Test.
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is Y):Y
Answering Y to the prompt will delete the snapshot.
.Notes
Last Updated: June 25, 2014
Version
: 1.0
.Link
Remove-VMSnapshot
#>
[cmdletbinding(SupportsShouldProcess,ConfirmImpact=High,DefaultParameterSetName=A
ll)]
Param (
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[string]$VMName=*,
[Parameter(Position=1)]
[ValidateScript({$_ -ge 1 })]
[Alias(days)]
[int]$Age=90,
[Parameter(Position=1,ParameterSetName=ByType)]
[ValidateNotNullorEmpty()]
[Alias(type)]
[Microsoft.HyperV.PowerShell.SnapshotType]$SnapshotType = Standard,
[ValidateNotNullorEmpty()]
[Alias(CN)]
[string]$computername = $env:computername
)
Write-Verbose -Message Starting $($MyInvocation.Mycommand)
#parameters for Get-VMSnapshot
$getParams= @{
Computername = $computername
ErrorAction = Stop
VMName = $VMName
}
if ($PSCmdlet.ParameterSetName -eq ByType) {
Write-Verbose Limiting snapshots to type $SnapshotType
$getParams.Add(SnapshotType,$SnapshotType)
}
Try {
[datetime]$Cutoff = ((Get-Date).Date).AddDays(-$Age)
Write-Verbose Searching for snapshots equal to or older than $cutoff on $computername
$snaps = Get-VMSnapshot @getParams | Where {$_.CreationTime -le $Cutoff }
}
Share it!
48
Catch {
Throw $_
}
if ($snaps) {
Write-Verbose Found $($snaps.count) snapshots to be removed
$snaps | Remove-VMSnapshot -IncludeAllChildSnapshots
}
Write-Verbose -Message Ending $($MyInvocation.Mycommand)
} #end function
By default the function will return all snapshots for all virtual machines older than 90 days.
You can specify a different number of days, filter the virtual machines by name or limit the
snapshots to a certain snapshot type. Because the command could potentially remove a lot of
files, it has support for WhatIf and Confirm.
PS C:\> remove-oldvmsnapshot -days 7 -computername chi-hvr2 -whatif
What if: Remove-VMSnapshot will remove snapshot Profile Cleanup Test.
What if: Remove-VMSnapshot will remove snapshot CHI-CORE01 Check 1.
What if: Remove-VMSnapshot will remove snapshot DCTEST Check 1.
If I re-ran the command without Whatif, these snapshots, including any child snapshots,
would be removed.
This command will use the standard Get-EventLog cmdlet to search the System event log
for anything logged from a Hyper-V source and select the first 100 entries. In this example, I
formatted the results as a table grouped by the source. You can see my results in Figure 14.
Share it!
49
Figure 14
This command will find all Hyper-V related errors and warnings on server CHI-HVR2 that have
been recorded in the last 3 days. Get-Eventlog searches classic event logs. Hyper-V also has
its own set of operational logs which you can query with Get-WinEvent.
PS C:\> Get-WinEvent -ListLog *Hyper-V*
LogMode
-------
Circular
1052672
146 Microsoft-Windows-Hyper-V-Config-Admin
Circular
1052672
0 Microsoft-Windows-Hyper-V-Config-Operational
Circular
1052672
0 Microsoft-Windows-Hyper-V-EmulatedNic-Admin
Circular
1052672
0 Microsoft-Windows-Hyper-V-Hypervisor-Admin
Circular
1052672
283 Microsoft-Windows-Hyper-V-HypervisorOperational
Circular
1052672
887 Microsoft-Windows-Hyper-V-Integration-Admin
Share it!
50
Circular
1052672
0 Microsoft-Windows-Hyper-V-SynthFc-Admin
Circular
1052672
Circular
1052672
1 Microsoft-Windows-Hyper-V-SynthStor-Admin
Circular
1052672
0 Microsoft-Windows-Hyper-V-SynthStor-
654 Microsoft-Windows-Hyper-V-SynthNic-Admin
Operational
Circular
1052672
0 Microsoft-Windows-Hyper-V-VID-Admin
Circular
1052672
Circular
1052672
38 Microsoft-Windows-Hyper-V-VMMS-Networking
Circular
1052672
60 Microsoft-Windows-Hyper-V-VMMS-Operational
Circular
1052672
40 Microsoft-Windows-Hyper-V-VMMS-Storage
Circular
1052672
2277 Microsoft-Windows-Hyper-V-VMMS-Admin
0 Microsoft-Windows-Hyper-V-VmSwitchOperational
Circular
1052672
1187 Microsoft-Windows-Hyper-V-Worker-Admin
In order to query a remote computer, you must configure a firewall exception for remote event
log management or use PowerShell remoting with Invoke-Command.
PS C:\> invoke-command {Get-WinEvent -ListLog *Hyper-V*} -ComputerName chi-hvr2
LogMode
PSComputerName
-------
--------------
Circular
1052672
0 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
0 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
0 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
0 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
Circular
1052672
Circular
1052672
0 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
Circular
1052672
0 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
0 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
0 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
Circular
1052672
30 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
36 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
4 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
0 Microsoft-Windows-Hyper-V-... chi-hvr2
Circular
1052672
You may want to limit your search to those logs that have entries.
PS C:\> invoke-command {Get-WinEvent -ListLog *Hyper-V* | where {$_.recordcount -gt 0} |
Select Logname,RecordCount} -ComputerName chi-hvr2
Share it!
51
LogName
: Microsoft-Windows-Hyper-V-Hypervisor-Operational
RecordCount
: 246
PSComputerName : chi-hvr2
RunspaceId
: b1ce1c35-4476-4684-9991-b9487f60bb51
LogName
: Microsoft-Windows-Hyper-V-Integration-Admin
RecordCount
: 1278
PSComputerName : chi-hvr2
RunspaceId
: b1ce1c35-4476-4684-9991-b9487f60bb51
...
Once you know the name of the log to query, you can run a simple command like this:
PS C:\> Invoke-Command {Get-WinEvent -LogName Microsoft-Windows-Hyper-V-Hypervisor-Operational -MaxEvents 10 } -computername chi-hvr2 | format-list
TimeCreated
: 6/19/2014 2:04:48 PM
ProviderName
: Microsoft-Windows-Hyper-V-Hypervisor
Id
: 16642
Message
PSComputerName : chi-hvr2
TimeCreated
: 6/19/2014 8:42:40 AM
ProviderName
: Microsoft-Windows-Hyper-V-Hypervisor
Id
: 16641
Message
PSComputerName : chi-hvr2
...
Here, Ive retrieved the 10 most recent entries from the Microsoft-Windows-Hyper-VHypervisor-Operational log. If you need to get more granular, you have a few options, but I
think using a hashtable of filtering options is the easiest approach. You can use code like this.
$filterHash = @{
LogName = Microsoft-Windows-Hyper-V-Config-Admin
Level = 2
StartTime = (Get-Date).AddDays(-7)
}
get-winevent -FilterHashtable $filterhash | format-list
Share it!
52
My result, which is all errors logged in the specified log over the last 7 days for the local host,
is shown in Figure 15.
Figure 15
Information messages are type 4 and warnings are type 3. Ive wrapped all of this into a
PowerShell function.
Get-HyperVEvents.ps1
#requires -version 3.0
Function Get-HyperVEvents {
<#
.Synopsis
Get errors and warnings from Hyper-V Operational logs.
.Description
This command will search a specified server for all Hyper-V related Windows Operational
logs and get all errors and warnings that have been recorded in the specified number of
days which is 7 by default.
The command uses PowerShell remoting to query event logs and resolve SIDs to account
names. The remote event log management firewall exception is not required to use the
command.
.Example
PS C:\> Get-HyperVEvents -Days 30 -computer CHI-HVR2 | Select LogName,TimeCreated,Type,
ID,Message,Username | Out-Gridview -title Events
Get all errors and warnings within the last 30 days on server CHI-HVR2 and display with
Out-Gridview.
Share it!
53
.Notes
Last Updated: June 25, 2014
Version
: 2.0
.Link
Get-WinEvent
Get-Eventlog
.Inputs
[String]
.Outputs
[System.Diagnostics.Eventing.Reader.EventLogRecord]
Technically this will be a deserialized version of this object.
#>
[cmdletbinding()]
Param(
[Parameter(Position=0,HelpMessage=Enter the name of a Hyper-V host)]
[ValidateNotNullorEmpty()]
[Alias(CN,PSComputername)]
[string]$Computername=$env:COMPUTERNAME,
[ValidateScript({$_ -ge 1})]
[int]$Days=7,
[Alias(RunAs)]
[System.Management.Automation.Credential()]$Credential = [System.Management.Automation.
PSCredential]::Empty
)
Write-Verbose Starting $($MyInvocation.MyCommand)
Write-Verbose Querying Hyper-V logs on $($computername.ToUpper())
#define a hash table of parameters to splat to Invoke-Command
$icmParams=@{
ErrorAction=Stop
ErrorVariable=MyErr
Computername=$Computername
HideComputername=$True
}
if ($credential.username) {
Write-Verbose Adding a credential for $($credential.username)
$icmParams.Add(Credential,$credential)
}
#define the scriptblock to run remotely and get events using Get-WinEvent
$sb = {
Param([string]$Verbose=SilentlyContinue)
#set verbose preference in the remote scriptblock
$VerbosePreference=$Verbose
#calculate the cutoff date
$start = (Get-Date).AddDays(-$using:days)
Write-Verbose Getting errors since $start
#construct a hash table for the -FilterHashTable parameter in Get-WinEvent
$filter= @{
Logname= Microsoft-Windows-Hyper-V*
Share it!
54
Level=2,3
StartTime= $start
}
#search logs for errors and warnings
#turn off errors to ignore exceptions about no matching records, which would be ok.
Try {
#add a property for each entry that translates the SID into
#the account name
Get-WinEvent -filterHashTable $filter -ErrorAction Stop | foreach {
#add some custom properties
$_ | Add-Member -MemberType AliasProperty -Name Type -Value LevelDisplayName
$_ | Add-Member -MemberType ScriptProperty -Name Username -Value {
[WMI]$Resolved = root\cimv2:Win32_SID.SID=$($this.UserID)
#write the resolved name to the pipeline
$($Resolved.ReferencedDomainName)\$($Resolved.Accountname)
} -PassThru
}
}
Catch {
Write-Warning No matching events found.
}
} #close scriptblock
#add the scriptblock to the parameter hashtable for Invoke-Command
$icmParams.Add(Scriptblock,$sb)
if ($VerbosePreference -eq Continue) {
#if this command was run with -Verbose, pass that to the scriptblock
#which will be running remotely.
Write-Verbose Adding verbose scriptblock argument
$sbArgs=Continue
$icmParams.Add(Argumentlist,$sbArgs)
}
Try {
#invoke the scriptblock remotely and pass properties to the pipeline, except
#for the RunspaceID from the temporary remoting session which we dont need.
Invoke-Command @icmParams
}
Catch {
#Invoke-Command failed
Write-Warning Failed to connect to $($computername.ToUpper())
Write-Warning $MyErr.errorRecord
#bail out of the function and dont do anything else
Return
}
#All done here
Write-Verbose Ending $($MyInvocation.MyCommand)
} #end function
This function assumes you will use PowerShell remoting to query the operational event logs.
By default, it gets all errors and warnings recorded in the last 7 days. I have included code to
Share it!
55
add an alias of Type (e.g. error) instead of having to know to use LogDisplayName, which isnt
exactly intuitive. There is also code to convert the user SID to a friendly name.
PS C:\> get-hypervevents -computername chi-hvr2
Figure 16
: Microsoft-Windows-Hyper-V-VMMS-Storage
: GLOBOMANTICS\Administrator
Type
: Error
Message
So, it seems I only have 1 entry recorded by a domain user as opposed to a system account.
This is what Im referring to:
Share it!
56
Count Name
----- ---1 GLOBOMANTICS\Administrator
4 NT VIRTUAL MACHINE\5FE62AF7-CE0A-477C-8DB4-E133CBC31C8F
4 NT VIRTUAL MACHINE\6164B819-D828-425C-823A-561D96EC0975
5 NT VIRTUAL MACHINE\E5099F2C-6489-4646-BD27-D0D519D6B0ED
5 NT VIRTUAL MACHINE\62E6858B-3B73-410E-8AF1-BA2B6F93ACA3
6 NT VIRTUAL MACHINE\60423BAE-36A4-441A-93B6-F2B2ABD9DBBC
18 NT VIRTUAL MACHINE\149E1B91-2B52-43E1-BDA6-BD6D89504BE1
40 NT AUTHORITY\SYSTEM
Specify the full path to the script file. By default, the script will create an HTML report for the
local computer using the specified path. The RecentCreated parameter is used to find virtual
machines that have been created in the last X number of days. The default is 30. The value
for LastUsed will show you virtual machines that have not been used in that number of days.
Share it!
57
The script will display all errors and warnings from all Hyper-V event logs recorded in the last X
number of hours. The default is 24.
The script will optionally return Hyper-V performance counter information (-Performance). If
you have metering enabled you can use Metering to display that information as well. Even
though the intent of metering is for monitoring usage, I think it offers another glimpse into the
overall health of the Hyper-V server and virtual machines.
Using the script, it is as easy as this to create the report:
PS C:\> C:\scripts\New-HVHealthReport.ps1 -Computername chi-hvr2 -path c:\work\hvr2.htm
-performance
Figure 17
Share it!
58
If you use Internet Explorer, you might be prompted to allow blocked content. Go ahead and
do so, as the page includes some code to collapse sections. You can click on a heading title to
toggle that section or on the +/- link at the top to toggle all sections.
Figure 18
Figure 19
Share it!
59
The report primarily shows data based on running virtual machines as you can see in Figure 19.
There is a lot of information in the report and you will have to run it for yourself.
You could define a normal variable in your profile and use that in your commands:
$hv = chi-hvr2
Get-VM -ComputerName $hv
Now, when I run Get-VM or any cmdlet that ends in VM, the ComputerName parameter will
automatically use the default value. You can always specify a different value.
This default parameter value only lasts for as long as your PowerShell session is open, so add
this command to your PowerShell profile if you always want this default.
Another tip, especially if you are new to PowerShell, is to expand nested properties or
collections of objects. Youve seen this in some recipes already. You might run a command
like this:
PS C:\> get-vm chi-win81 -computername chi-hvr2 | select VMIntegrationService
VMIntegrationService
-------------------{Time Synchronization, Heartbeat, Key-Value Pair Exchange, Shutdown...}
PowerShell will expand the property and write each nested object to the pipeline.
Share it!
60
Figure 20
Finally, even though you can directly modify virtual machines and other objects using the Set-*
cmdlets, I prefer to first use a Get-* command to verify I am selecting the right objects.
PS C:\> get-vm -Name Demo*
Once I am satisfied, I can press the up arrow to bring the last command back and append the
necessary Set, Start, Stop or whatever command.
PS C:\> get-vm -Name Demo* | Start-VM
And as an added level of safety, dont forget to see if the cmdlet supports the WhatIf
parameter.
Additional Resources
I hope the first place you go to for additional PowerShell and Hyper-V resources is the Altaro
Hyper-V blog at http://www.altaro.com/hyper-v/. As you might expect, my own blog (http://
jdhitsolutions.com/blog) includes a great deal of PowerShell content. If you are struggling
to get started with PowerShell you can find my most current listing of essential books and
training material at http://jdhitsolutions.com/blog/essential-powershell-resources/.
Share it!
61
About Altaro
Altaro Software (www.altaro.com) is a fast growing developer of easy to use backup solutions
targeted towards SMBs and focused on Microsoft Hyper-V. Altaro take pride in their software
and their high level of personal customer service and support, and it shows; Founded in 2009,
Altaro already service over 15,000 satisfied customers worldwide and are a Gold Microsoft
Partner for Application Development.
Share it!
62
Follow Altaro
Like our eBook? Theres more!
Subscribe to our Hyper-V blog http://www.altaro.com/hyper-v/ and receive best practices,
tips, free Hyper-V PowerShell scripts and more here: http://www.altaro.com/hyper-v/sign-up/
Share it!
63