Get-VHD works localy but not remotely

Hi, I am trying to list all VHDs on hyper-V using Get-VHD, locally on the server it returns good results , here is the command
Get-VM -VMName * | Select-Object VMId | Get-VHD | Format-Table

but when I try to run the same command from my laptop using
Get-VM -ComputerName myserver -VMName * | Select-Object VMId | Get-VHD | Format-Table

it returns errors as follows

Get-VHD: Hyper-V was unable to find a virtual machine with id “2ba8bbbb-ced9-40ec-832f-501cd97a6a49”.
Get-VHD: Hyper-V was unable to find a virtual machine with id “86ee4914-83a2-4b76-8b5a-db0bbdf90743”.
Get-VHD: Hyper-V was unable to find a virtual machine with id “e5d0d14a-3bed-4a0e-9805-3e7f8522698a”.


Am I missing something? Thanks for your help

I’m not sure why Get-VHD doesn’t like you piping in the VMID.
However, Get-VHD already has a VMID Paramter so I would just avoid all of that and type. . .

Get-VHD -VMId "2ba8bbbb-ced9-40ec-832f-501cd97a6a49” | Format-Table

Also on your server(s) when Installing Hyper-V, I recommend adding the role from PowerShell and make sure that you specify the two switches that begin with “include” and thn TAB complete them but here they are. . .

Install-WindowsFeature -Name Hyper-V -IncludeAllSubFeature -IncludeManagementTools

Because I have seen server manager or add/remove programs not always install the PowerShell module for Hyper-V. The sam goes for Windows Clustering, it will miss the PowerShell module unless you force it to or just use PowerShell to add features.

Thanks cern, the module is installed since lomg time. I need to get the details for all VMs not only one

Without having any experience with this module I could imagine that you should help the cmdlet Get-VHD to find the server you’re dealing with … like so …

Get-VM -ComputerName myserver -VMName * | 
    Select-Object VMId | 
        Get-VHD -ComputerName myserver | 
            Format-Table

But actually I’d expect to work like this:

Get-VM -ComputerName myserver -VMName * | 
    Get-VHD | 
        Format-Table

IF that does not work you could use a loop and provide the VMId and the ComputerName explicitly via command line

This is my first thought.
When you run Get-VM and Get-VHD locally on the server it’s all good because you’re referring to VMs and VHDs that exist on that server.
When you run Get-VM -ComputerName myserver it’s correctly going out to the server an enumerating the VMs and returning those objects. But when you pipe those objects to Get-VHD it has no idea they exist on another computer so it’s just looking up those VMIds locally and coming up with nothing.
Try something like this instead:

Get-VM -ComputerName myserver -VMName * | Select-Object VMId | Get-VHD -ComputerName myserver | Format-Table

Ideally I would break up this one-liner in to pieces so I could manually iterate through the results and try things to see where the failure is:

$VMs = Get-VM -ComputerName myserver -VMName *
$VMS
# returns the results of the previous command for me to look at an inspect
$VMs[0] | Get-VHD
# try sending one of those objects to Get-VHD to see what happens, if I don't get the result I expected then I can try something else
$VMs[0] | Get-VHD -ComputerName myserver
# cool, maybe that worked, if it did then I can move on
$VHDs = $VMs | Select-Object VMId | Get-VHD -ComputerName myserver
# now I can call the $VHDs variable and confirm it has what I want
$VHDs
# and then I can do it again and pipe it to Format-Table or Export-Csv, or Out-Gridview, whatever I want, and I don't have to rerun all of the previous commands just to change that last part.

So many thanks, I got it and your suggestions work fine by adding -ComputerName myserver. However, I am trying to write a script to get all details of VMs to do a weekly reporting on Hyper-V VMs

With the following command, I get some fields, not all fields I need

Get-VM -ComputerName SalamCOGRETOUR | % { $vhd = Get-VHD -ComputerName SalamCOGRETOUR -VmId $_.VmId; $vhd | Add-Member -NotePropertyName "Name" -NotePropertyValue $_.Name; $vhd; } |
Select-Object -Property Name, path,computername,vhdtype, @{label='Size(GB)';expression={$_.filesize/1gb -as [int]}} | Export-Csv -Path "output.csv" -NoTypeInformation

I get some details as follows

with another command I managed to construct

Get-VM -ComputerName SalamCOGRETOUR | Select-Object Name,ProcessorCount, VMID, `
@{name='MemoryStartup';expression={[math]::Round($_.MemoryStartup/1GB,0).tostring() + ' GB'}},`
@{name='MemoryAssigned';expression={[math]::Round($_.MemoryAssigned/1GB,0).tostring() + ' GB'}},`
@{n='Uptime';e={(Get-Date) - $_.Uptime}},State,Version | Export-Csv -Path "output1.csv"

I get other details as follows

By the end of the day, I need to understand/figure out how to merge or update my scripts to give an output as follows without me needing to copy/paste between 2 files

Thanks again for your help

One thing to note is that I needed to go through those 2 scripts for 1 reason, the Get-VHD does not return the VM name. I came across a syntax

Get-VM -ComputerName SalamCOGRETOUR | Select-Object -ExpandProperty HardDrives

where it helped getting the Hard drives with the VM name as follows

but I was not able to use this -ExpandProperty HardDrives in my 2 scripts.

I did
Get-Command Get-VM | Get-members
I dont see any indication about all propertied that I can expand, also googled but did not get any useful information. So the question is how can I get a list of all properties that I can expand?

For me that would be the wrong question. I’d ask you what properties do you really need? :man_shrugging:

If I got you right you just need to combine the queries in a loop and use a PSCustomObject to output it all together.

And BTW:

You should not use backticks as line continuation character. Especially when you have commas there anyway as they are working as line continuation character.

OK,
This is what I have never done using PSCustomObject, can you share an example for my 2 scripts please?

There are literally thousands of examples out there … even here in this very forum. Spend a little effort to do your own research, please.

Really thanks for your help, after spending a couple of hours, I managed to create the following script but csv file is empty by the end and no errors generated

$VMS = @()

$VMDetailsProc = Get-VM -ComputerName SalamCOGRETOUR | Select-Object Name,ProcessorCount, VMID, @{name='MemoryStartup';expression={[math]::Round($_.MemoryStartup/1GB,0).tostring() + ' GB'}}, @{name='MemoryAssigned';expression={[math]::Round($_.MemoryAssigned/1GB,0).tostring() + ' GB'}}, @{n='Uptime';e={(Get-Date) - $_.Uptime}},State,Version `
|  `
ForEach-Object {
    $VMgetDetails = Get-VM -ComputerName SalamCOGRETOUR | % { $vhd = Get-VHD -ComputerName SalamCOGRETOUR -VmId $_.VmId; $vhd | Add-Member -NotePropertyName "Name" -NotePropertyValue $_.Name; $vhd; } |
Select-Object -Property Name, path,computername,vhdtype, @{label='Size(GB)';expression={$_.filesize/1gb -as [int]}}  
	
   $VMS += [PSCustomObject]@{
        Hostname   = $VMgetDetails.Name
        HD         = $VMgetDetails.Path
		ProcessorCount = $VMDetailsProc.ProcessorCount
        MemoryStartup = $VMDetailsProc.MemoryStartup
        MemoryAssigned = $VMDetailsProc.MemoryAssigned
    }

} $VMS | Export-Csv -Path "outputMerge.csv" -NoTypeInformation

I tried s many scenarios but not able to get the right details, mainly fields from 1st query and not getting passed in the loop

move your last part to its own line

}
$VMS | Export-Csv -Path "outputMerge.csv" -NoTypeInformation

edit to add:
Now you’re using PSCustomObject which I think makes it a lot easier to control your output, but the lines preceding it are still very confusing and hard read. I suggest breaking it up more in to chunks. Right now your PSCustomObject has 5 properties but your first array of objects ($VMDetailsProc) has 8 properties. You’re getting a bunch of details and then not using them.

Take that end result spreadsheet you’ve got and figure out every property you want, then right that as your PSCustomObject, then work backwards and figure out how to populate each one of those properties with the required value.

I did but the output is rubbish

it looks like it’s because you’re doing a Select-Object statement and then adding PScustomObjects to an array. Again, you’ve got too much going on in long one-liners making it difficult to track what you’re doing.

I am trying to gather all details for VMs on a hyper-V host for reporting purposes. Get-VM does not return hard drive details so that is why I using another query to get them and the idea is to merge output for both queries for each VM.
From 1st query, I get some details then I loop on all records and try to get the other missing details in the 2nd query inside the loop

Do you mean that I should not use Select-Object? If yes, what is the alternative? Thanks

$Properties = @(
    "ComputerName",
    "Name",
    "ProcessorCount",
    "VMID",
    @{Name='MemoryStartup';Expression={[math]::Round($_.MemoryStartup/1GB,0).tostring() + ' GB'}},
    @{Name='MemoryAssigned';Expression={[math]::Round($_.MemoryAssigned/1GB,0).tostring() + ' GB'}},
    @{Name='Uptime';Expression={(Get-Date) - $_.Uptime}},
    "State",
    "Version" 
)

$VMResources = Get-VM -ComputerName SalamCOGRETOUR | Select-Object -Property $Properties

$VMDetails = Foreach ($VM in $VMResources) {
    Get-VHD -VMid $VM.VMid | Foreach-Object {
        [PSCustomObject]@{
            ComputerName = $VM.ComputerName
            Name = $VM.Name
            ProcessorCount = $VM.ProcessorCount
            VMID = $VM.VMId
            MemoryStartup = $VM.MemoryStartup
            MemoryAssigned = $VM.MemoryAssigned
            Uptime = $VM.Uptime
            State = $VM.State
            Version = $VM.Version
            Path = $_.Path
            'Size(GB)' = $_.FileSize/1GB -as [int]
            VHDType = $_.VhdType
        }
    }
}

$VMDetails | Export-Csv -Path "outputMerge.csv" -NoTypeInformation

Let me explain.
I’d already written out in a notepad all of the properties I wanted to get for the resulting CSV and that was something like:

ComputerName 
Name 
ProcessorCount 
VMID 
MemoryStartup 
MemoryAssigned 
Uptime 
State 
Version 
Path 
Size(GB) 
VHDType 

First thing I did was something like:

$VMs = Get-VM

And then I explored what kinds of properties those objects had using Get-Member and Select-Object

$VMs[0] | Get-Member
$VMs[0] | Select-Object *

I saw that while there was a HardDrives property those objects didn’t have the file size or VHDType. That info was returned by Get-VHD as you’ve seen. So I made an array of properties that I’m going to select with Select-Object because this allows me to write it vertically and makes it much easier to look at.

Then I do an initial run of Get-VM and pipe it to Select-Object using that array of properties and I store it all in $VMResources. Now i’ve got most of the details I need I just need information about the disks which I’m going to get from Get-VHD.
I again spent some time manually exploring Get-VHD and the output and looking at your picture of your spreadsheet. I don’t want empty cells in Excel for things like VMid or Uptime and such because I want to be able to filter the table on anything and have all the details there. This meant that I would need an object PER vhd that had all of the properties I would ultimately want in the spreadsheet.

I do a foreach loop through my original $VMResources and do a Get-VHD on each one of those VMs, which will likely return multiple VHD objects so I pipe that to a Foreach-Object.
Inside that loop is when I create my PSCustomObjects containing alllll of the properties I’m going to want to output. Some of the values of those properties come from the original $VM object in the outer loop, and some of the properties from from the inner loops “current object” $_ value.
I capture it all in a variable called $VMDetails so I can manually look one more time and make sure that contains all of the things I want an dchange anything I need to change. Then finally I write the standalone line to export that array to a CSV.

3 Likes

Fantastic, it works like a charm, really appreciate your help. I was doing get member on Get-VM and not on VM as you do where we cam see all properties, my bad. A special thanks for the clear and concise explanation.
one note, as indicated earlier, I needed to add -ComputerName SalamCOGRETOUR on line 16 to get-vhd working
Get-VHD -ComputerName SalamCOGRETOUR
OK, now I have very simple question: Get-VM ha the argument -ComputerName which can be a collection as follows
server1, server2.....
As I have 3 Hyper-V hosts, I need to run the script against the 3 in 1 run
I tried to add a 2nd server to both lines Get-VM and Get-VHD but it errors
Line 13 : $VMResources = Get-VM -ComputerName SalamCOGRETOUR, salam7 | Select-Object -Property $Properties
and
Line 16 : Get-VHD -ComputerName SalamCOGRETOUR, salam7 -VMid $VM.VMid | Foreach-Object {

I tried the multi server by reading list of hyper-V servers in text file then do a loop before line 14 and it worked wiyhout error but the ouput in the file was for the last hyper-V only, maybe I need an append attribute , not sure

In the easiest way you make the Hyper-V host name a parameter for the hole piece of code and run it in a loop over the 3 hosts. In the end you tell Export-Csv to -Append the results to an existing file and you’re done.

I urgently recommed to read the

By making your code easier to read you make it easier to to understand and to maintain or to extend if needed.