Analyze your VHD(x) usage with PowerShell

In this case, I wanted a quick analysis on my Hyper-V VM’s vhd files and get the disk size and free disk space before upgrading to Server 2012 R2 Hyper-V.

The snippet basically loops trough all VM’s on a host (or in a cluster if you would want that), and gives you output as below.

2014-01-11 21-01-04

2014-01-11 21-01-23

If you want information on all disks for all VM’s in a cluster you can get that with Get-VHDStat -Cluster ClusterName.

Get-VHDStat

function Get-VHDStat {
param(
[Parameter(Mandatory=$false)]
[string]
$Cluster
)

if ($Cluster) {
    $VMs = Get-ClusterGroup -Cluster $cluster | where grouptype -eq 'virtualmachine' | Get-VM
} else {
    $VMs = Get-VM 
}
	foreach ($VM in $VMs){
		$VHDs=Get-VHD $vm.harddrives.path -ComputerName $vm.computername
            foreach ($VHD in $VHDs) {
        	    New-Object PSObject -Property @{
                    Name = $VM.name
				    Type = $VHD.VhdType
				    Path = $VHD.Path
				    'Total(GB)' = [math]::Round($VHD.Size/1GB)
                    'Used(GB)' = [math]::Round($VHD.FileSize/1GB)
				    'Free(GB)' =  [math]::Round($VHD.Size/1GB- $VHD.FileSize/1GB)
                 }
	        }
    }
}

Enjoy!

/Johan

Advertisement

23 thoughts on “Analyze your VHD(x) usage with PowerShell

  1. Schollb

    Adding “path” to the statement “Get-VHDStat | select name, Path, Type, Total*,Free* | sort-object Free* ” but now it does not display in a list view. It comes out more like your first screen shot, how can I keep all info for each VHD/VHDX file on one line?

    Reply
    1. Johan Dahlbom Post author

      Hi,
      Thanks for Reading!
      Get-VHDStat | select name,path,Type,Total*,Free* | sort-object Free* | Format-Table -AutoSize
      Will give you what you want
      If you instead want to export it to a csv file, replace Format-Table -AutoSize with Export-csv filename.csv -NoTypeInformation

      /Johan

      Reply
      1. Pär Wallin

        Hi, I have copy your script and tried to use this script but it wont show anything, two questions:
        1. Should this be executed on any host server or vmm server?
        2. where in the code do you add?
        Get-VHDStat | select name,path,Type,Total*,Free* | sort-object Free* | Format-Table -AutoSize

      2. Johan Dahlbom Post author

        Hi Pär,

        Since it’s a function, you just load the code on any machine that has the cluster / hyper-v powershell modules. After loading the code (you do it by just putting it in a file and run it by doing ‘. .\Code.ps1’, or simply copy-paste it in to a powershell window running as administrator).
        After doing that, you can run the line ‘Get-VHDStat | select name,path,Type,Total*,Free* | sort-object Free* | Format-Table -AutoSize

        Let me know if you have further questions!
        /Johan

  2. Jose

    Hey sir.. im getting lots of errors from your script..

    Im trying to run on windows 8 but don’t thinks is related

    At line:20 char:49
    + ‘Total(GB)’ = [math]::Round($<span class="skimlinks-unlinked …
    + ~
    Missing ')' in method call.
    At line:20 char:49
    + 'Total(GB)' = [math]::Round($<span class="skimlinks-unlinked …
    + ~~~~~~
    Unexpected token '$<span' in expression or statement.
    At line:20 char:49
    + 'Total(GB)' = [math]::Round($<span class="skimlinks-unlinked …
    + ~
    The hash literal was incomplete.
    At line:15 char:37
    + foreach ($VHD in $VHDs) {
    + ~
    Missing closing '}' in statement block.
    At line:13 char:26
    + foreach ($VM in $VMs){
    + ~
    Missing closing '}' in statement block.
    At line:1 char:22
    + function Get-VHDStat {
    + ~
    Missing closing '}' in statement block.
    At line:20 char:102
    + … Size/1GB)
    + ~
    Unexpected token ‘)’ in expression or statement.
    At line:22 char:49
    + ‘Free(GB)’ = [math]::Round($<span class="skimlinks-unlinked …
    + ~
    Missing ')' in method call.
    At line:22 char:49
    + 'Free(GB)' = [math]::Round($<span class="skimlinks-unlinked …
    + ~~~~~~
    Unexpected token '$<span' in expression or statement.
    At line:22 char:121
    + … HD.FileSize/1GB)
    + ~
    Unexpected token ')' in expression or statement.
    Not all parse errors were reported. Correct the reported errors and try again.
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : MissingEndParenthesisInMethodCall

    Thanks

    Reply
      1. Jose

        That was it.. Thanks a lot Johan, now its working.. I have another question.. how can I put all disks within a server on the same line?? I was thinking about separate them by – or , but I don’t know how to do it..
        I would like to see something like this

        Name Type Total Free
        Server1 Dynamic 500 10
        Server2 – Server2 Dynamic – Differential – 250 – 100 15 -25

        I need that because im adding to a csv and I need all info per server on one line

        Thanks one more time

  3. Ruggiero

    Added Totals:
    function Get-VHDStat {
    param(
    [Parameter(Mandatory=$false)]
    [string]
    $Cluster
    )
    $TotalGB = 0
    $UsedGB = 0
    $FreeGB = 0

    if ($Cluster) {
    $VMs = Get-ClusterGroup -Cluster $cluster | where grouptype -eq ‘virtualmachine’ | Get-VM
    }
    else {
    $VMs = Get-VM
    }
    foreach ($VM in $VMs){
    # write-host ‘VM:’ $VM.name ‘PATH:CLS’ $VM.harddrives.path
    $VHDs=Get-VHD $VM.harddrives.path -ComputerName $VM.computername
    foreach ($VHD in $VHDs) {
    $TotalGB += [math]::Round($VHD.Size/1GB)
    $UsedGB += [math]::Round($VHD.FileSize/1GB)

    New-Object PSObject -Property @{
    Name = $VM.name
    Type = $VHD.VhdType
    Path = $VHD.Path
    ‘Total(GB)’ = [math]::Round($VHD.Size/1GB)
    ‘Used(GB)’ = [math]::Round($VHD.FileSize/1GB)
    ‘Free(GB)’ = [math]::Round($VHD.Size/1GB- $VHD.FileSize/1GB)
    }

    }

    }
    New-Object PSObject -Property @{
    Name =’Total’

    ‘Total(GB)’ = $TotalGB
    ‘Used(GB)’ = $UsedGB
    ‘Free(GB)’ = ($TotalGB – $UsedGB)
    }

    }

    Reply
  4. Nathan GracieRaitt (@NathanGracie)

    Hi. Thanks for sharing a nice bit of script. I have an environment with a mix of ClusterSharedVolume storage and SMI-S integrated storage. This script doesn’t currently have any ability to look in to the state of the VHDs on the SMI-S integrated LUNs.

    SCVMM uses SMI-S to dynamically assign LUNs to the Hyper-V hosts currently running whatever VM is storing VHDs in that LUN. I’m therefore imagining that I’m going to need to connect to each host in the cluster and enumerate LUNs for the non-CSV-based VMs on that host, and interrogate the VHDXs on those LUNs.

    I think I’m going to have to do a bit of work to extend this for that specific requirement, but just thought I’d check in here to see if you’ve already run into the same requirement and figured out a way to do this?

    Reply
  5. Antonio Gutierrez

    Thanks for the post Johan, I tried to make a line of code that retrieves basically the same as your script but it doesn’t return what I wanted, as far as I can tell Size and FileSize are only useful with dynamic disks, with fixed disk I can’t get how much of it is actually used, do you know any way of accomplish the task with fixed disks? Below is the one liner just in case someone is interested, regards.

    Get-VM | select @{l=”ComputerName”;e={$_.Name}},@{l=”Path”;e={$_.harddrives.path}} | Get-VHD | select @{l=”Path”;e={$_.Path}},@{l=”FileSize(GB)”;e={$_.FileSize / 1GB}},@{l=”Size(GB)”;e={$_.Size / 1GB}},@{l=”PercentUsage”;e={$_.FileSize * 100 / $_.Size}}

    Reply
  6. gtzantonio

    Thanks Johan, I made a line of code based on your script to check data on some vhd files, but for me it only works if the disk is dynamic, is there a way to inspect the usage of a fixed disk? Below is the one liner in case someone is interested, thank you.

    Get-VM | select @{l=”ComputerName”;e={$_.Name}},@{l=”Path”;e={$_.harddrives.path}} | Get-VHD | select @{l=”Path”;e={$_.Path}},@{l=”FileSize(GB)”;e={$_.FileSize / 1GB}},@{l=”Size(GB)”;e={$_.Size / 1GB}},@{l=”PercentUsage”;e={$_.FileSize * 100 / $_.Size}}

    Antonio

    Reply
  7. Bryant

    Hi John,
    I wonder if you could teach me to these as well:
    – current storage size
    – maximum storage size
    – current cpu usage
    – maximum cpu usage
    – current memory usage
    – maximum memory usage
    of all the VM’s

    Reply
  8. Stefan

    Hello, I’m a newbie on Powershell….. i don’t seem to get this working. After having saved your code to a .ps1 file, I can’t call it up in powershell.. What do I have to do?
    Thanks for your help

    Reply
  9. bruno Silva

    WOW!

    Awesome!

    I’m looking for some vhdx “lost” on CSV and find with Vm is using him

    Thnks a lot!

    Reply
  10. Charlie Coverdale

    Hi Johan,

    I get an error followed by “some” results …

    PS C:\Windows\system32> Get-VHDStat | select name,path,Type,Total*,Free* | sort-object Free* | Format-Table -AutoSize
    Get-VHD : Cannot validate argument on parameter ‘Path’. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
    At line:13 char:23
    + $VHDs=Get-VHD $vm.harddrives.path -ComputerName $vm.computername
    + ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Get-VHD], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.Vhd.PowerShell.GetVhdCommand

    Name Path Type Total(GB) Free(GB)
    —- —- —- ——— ——–
    SDC-DEVVSRP02 C:\ClusterStorage\Volume1\SDC-DEVVSRP02\SDC-DEVVSRP02_F.vhdx Fixed 20 0
    SDC-CDLTVSQ01 C:\ClusterStorage\DevVOL10\SDC-CDLTVSQ01\SDC-CDLTVSQ01_disk_d.vhdx Fixed 300 0
    SDC-DEVVSRP02 C:\ClusterStorage\volume1\sdc-devvsrp02\SDC-DEVVSRP02_C.vhdx Fixed 80 0
    SDC-DEVVBNK02 C:\ClusterStorage\DevVOL03\SDC-DEVVBNK02\c_drive.vhd Dynamic 140 0

    Reply
  11. Philip Elder

    Hi there,

    Something seems to be missing when running this script on a cluster. The variable $Cluster does not seem to get any kind of output assigned to it?

    When I run this on one node I only get output for that node.

    Reply
  12. Philip Elder

    I got it figured out. The updated version is:

    function Get-VHDStat {
    param(
    [Parameter(Mandatory=$false)]
    [string]
    $Cluster = (Get-Cluster)
    )

    if ($Cluster) {
    $VMs = Get-ClusterGroup -Cluster $cluster | Where-Object grouptype -eq ‘virtualmachine’ | Get-VM
    } else {
    $VMs = Get-VM
    }
    foreach ($VM in $VMs){
    $VHDs=Get-VHD $vm.harddrives.path -ComputerName $vm.computername
    foreach ($VHD in $VHDs) {
    New-Object PSObject -Property @{
    Name = $VM.name
    Type = $VHD.VhdType
    Path = $VHD.Path
    ‘Total(GB)’ = [math]::Round($VHD.Size/1GB)
    ‘Used(GB)’ = [math]::Round($VHD.FileSize/1GB)
    ‘Free(GB)’ = [math]::Round($VHD.Size/1GB- $VHD.FileSize/1GB)
    }
    }
    }
    }

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s