2

I am attempting to use a script that shuts down my Hyper-V VMs, copies their VHDs to a target share, then boots them back up. I found the script on spiceworks just a couple days ago. I always receive errors that the system name intended for shutdown is a null value.

General info:

  • I have been running powershell as an admin.
  • I set my ExecutionPolicy to "RemoteSigned" so that local powershell files will execute.
  • I appear to be running PowerShell v 2.0
  • I am running HyperV in Server 2008 R2
  • I am executing this script directly on the VM host

Below is the script and an explanation of where I encounter the error and exactly what that error is:

 $waitstart = 200
 $waitshutdown = 120


 if ($args[1] -match "0") {
 $inputfile=get-content $args[0]
 foreach ($guest in $inputfile) {
 write-host "Starting $guest"
 $vm = gwmi -namespace root\virtualization -query "select * from msvm_computersystem where elementname='$guest'"
 $result = $vm.requeststatechange(2)
 if ($result.returnvalue -match "0") {
 start-sleep -s $waitstart
 write-host ""
 write-host "$guest is started" -foregroundcolor green
 write-host ""
 }
 else {
 write-host ""
 write-host "unable to start $guest" -foregroundcolor red
 write-host ""
 }}}

 if ($args[1] -match "1") {
 $inputfile=get-content $args[0]
 foreach ($guest in $inputfile) {
 write-host "shutting down $guest"
 $vm = gwmi -namespace root\virtualization -query "select * from msvm_computersystem where elementname='$guest'"
 $vmname = $vm.name
 $vmshut = gwmi -namespace root\virtualization -query "SELECT * FROM Msvm_ShutdownComponent WHERE SystemName='$vmname'"
 $result = $vmshut.InitiateShutdown("$true","no comment")
 if ($result.returnvalue -match "0") {
 start-sleep -s $waitshutdown
 write-host ""
 write-host "no error while shutting down $guest"
 write-host "shutdown of $guest completed" -foregroundcolor green
 write-host ""}

 else {
 write-host ""
 write-host "unable to shutdown $guest" -foregroundcolor red
 write-host ""
 }}}

 else {
 write-host "USAGE: to shutdown VMs," -nonewline; write-host ".\managehyperV.ps1 c:\hosts.txt 1" -foregroundcolor yellow
 write-host "USAGE: to start VMs," -nonewline; write-host ".\managehyperV.ps1 c:\hosts.txt 0" -foregroundcolor yellow
 }

The script takes in the argument "1" or "0" to determine whether the VMs in the "guests" text list should be shut down or started.

Running Powershell as an admin I can successfully run the following query:

 $vm = gwmi -namespace root\virtualization -query "select * from msvm_computersystem where elementname='$guest'"

This returns a string of some kind that represents the system name of the VM.

However, the following query always returns a null value:

 $vmshut = gwmi -namespace root\virtualization -query "SELECT * FROM Msvm_ShutdownComponent WHERE SystemName='$vmname'"

It seems as though the 'Msvm_ShutdownComponent' class doesn't exist on my system... when I execute the following line:

  $result = $vmshut.InitiateShutdown("$true","no comment")

I always receive an error that states "You cannot call a method on a null-valued expression". I have spent about a day and a half trying to find the mistake I am making that causes this, but I can't narrow it down.

Shrout1
  • 343
  • 2
  • 6
  • 18
  • `I appear to be running PowerShell v 1.0` - I doubt that, you are probably using at least 2.0. What do you see on `$PSVersionTable`. – Zoredache Mar 25 '14 at 21:02
  • Zoredache - you appear to be correct; when running that command PS version is listed as "2.0". I haven't used powershell very extensively. – Shrout1 Mar 26 '14 at 15:56

1 Answers1

0

I have created a script that meets my needs and I will post it here.

This script performs the following tasks:

  • Iterates through all listed physical hosts
  • Iterates through all non-excluded VMs
  • Gracefully shuts down VMs & waits for shutdown to complete
  • Performs synchronous BITS transfer of all VHD files (one at a time) for target VMs to specified location
  • Boots target VM after transfer is complete

This script can operate on remote physical hosts and does not need to be run locally. Be sure to run the script with administrative credentials or it will not work correctly.

I am using Windows Server 2008 R2 and so I don't have access to all the fancy commands available in Windows Server 2012.

Also, this is the ultimate expression of "script kiddie" here; I've mashed together about 4 different scripts and then added some logic on top of it. Forgive the inconsistencies in syntax etc.

This script was hand transcribed from a standalone corporate sysem I use, so it may have a few spelling errors. I haven't tried to execute this script directly. The one on my system seems to work just fine.

#Run this script in an administrative instance of powershell
#This script executes based on files in the C:\Scripts directory
cd "C:\Scripts"
#Import the BITS Library so that synchronous transfers can occur easily
Import-Module BitsTransfer
#Note that all these files are delimited by carriage returns - 1 host/vm name per line
#Place these files in the "C:\Scripts" directory on the system running this script

#HyperVParents is the list of remote systems that are hosting VMs that need backup.
$HyperVParents = Get-Content HyperV_Hosts.txt

#ExcludedVMs is the list of VMs that will not be turned off or booted during this process
#NOTE: All VM VHD files in Hyper-V are backed up with this script.
$ExcludedVMs = Get-Content ExcludedVMs.txt

#Build Date String
$filedate = get-date -format "M-d-yyyy"

#Create target directory on remote server for backup if it is not already present
#Replace REMOTESRV with your servername
$TargetPath = "\\REMOTESRV\VHDs\" + $filedate
If(!(Test-Path -Path $TargetPath))
    {
        New-Item -ItemType directory -Path $TargetPath
    }

Foreach ($HyperVParent in $HyperVParents)
{
$VMManagementService = Get-WmiObject -class "Msvm_VirtualSystemManagementService" -namespace "root\virtualization" -ComputerName $HyperVParent
$VMs = Get-WmiObject -Namespace "root\virtualization" -ComputerName $HyperVParent -Query "Select * From MSVM_ComputerSystem where Caption='Virtual Machine'"

Foreach ($VM in $VMs)
    {
    #Set $VMExcluded to null in order to test the next VM for exclusion
    #This routine could probably be more efficient.
    $VMExcluded = $null
    #Loop through list of excluded VMs to see if current VM is within this list
    Foreach ($ExcludedVM in $ExcludedVMs)
    {
        If ($VM.ElementName -eq $ExcludedVM)
        {
            $VMExcluded = $true
            Write-Host $VM.ElementName, ": Excluded from startup/shutdown process"
        }
    }

    $VMSettingData = Get-WmiObject -Namespace "root\virtualization" -Query "Associators of {$VM} Where ResultClass=Msvm_VirtualSystemSettingData AssocClass=Msvm_SettingsDefineState" -ComputerName $HyperVParent
    $VirtualDiskResource = Get-WmiObject -Namespace "root\virtualization" -Query "Associators of {$VMSettingData} Where ResultClass=Msvm_ResourceAllocationSettingData AssocClass=Msvm_VirtualSystemSettingDataComponent" -ComputerName $HyperVParent | Where-Object {$_.ResourceSubType -match "Microsoft Virtual Hard Disk"}
    $VHD_Path = $null
    $vmGUID = $VM.name

    #Start logical check that skips unused systems. They will not be powered off or on.
    If ($VMExcluded -eq $null)
    {
        $Query = "select * from Msvm_computersystem where Name='" + $vmGUID + "'"
        $VMTemp = gwmi -namespace root\virtualization -query $Query -ComputerName $HyperVParent

        #Only attempt a shutdown if the system is powered on
        If ($VMTemp.EnableState -ne "3")
        {
            Write-Host "System Requires Shutdown:", $VM.ElementName

            #Build SQL Query - Use the target GUID here since the Msvm_ShutdownComponent seems to be only targeted with a GUID
            $ShutdownQuery = "SELECT * from Msvm_ShutdownComponent WHERE SystemName ='" + $vmGUID + "'"
            #Execute the query to select the shutdown component of the target VM
            $vmshut = gwmi -namespace root\virtualization -query $ShutdownQuery -ComputerName $HyperVParent
            $result = $vmshut.InitiateShutdown("$true","VHD Backup Process");

            Write-Host "Shutting Down:", $VM.ElementName

            #Wait for system to shutdown
            Do {
                #Increment 1 sec pauses for each loop
                Start-Sleep -s 1
                #Use a different variable here so that the original target VM is not modified
                #Requery the VM each second to get an updated EnabledState
                $VMTemp = gwmi -namespace root\virtualization -query $Query -ComputerName $HyperVParent
                }
            While ($VMTemp.EnabledState -ne "3");
            Write-Host $VM.ElementName, "successfully shutdown"
        }
        Else
        {
            Write-Host $VM.ElementName, ": Already shutdown"
        }
    }
    #End Logical check for systems that are unused

    #Perform the file transfer of the VHD file afer the VM has shut down.
    Foreach ($VHD in $VirtualDiskResource)
        {
            $VHD_PATH = $VHD.Connection[0] + ","

            #Replace colon in path with dollar sign for UNC purposes
            $VHD_PATH_TEMP = $VHD.Connection[0] -replace ':','$'

            #Build Source String
            $BackupSource = "\\" + $HyperVParent + "\" + "VHD_PATH_TEMP"

            #Extract VHD Name from the path for use in the target file
            $VHDName = Split-Path -Leaf $VHD.Connection[0]

            #Build Target String; $TargetPath was built at the initiation of the script
            $BackupTarget = $TargetPath + "\" + $VHDName

            Write-Host "Beginning Backup:", $VM.ElementName, $BackupSource

            #Transfer VHD file to the target server using BITS
            StartBitsTransfer -Source $BackupSource -Destination $BackupTarget

            Write-Host "Backup Complete:", $VM.ElementName, $BackupTarget
        }

        #Here backed up systems are turned back on after the file transfer of their VHD is complete.
        #Filter out certain unused VMs - unused systems do not need to be booted.
        If ($VMExcluded -eq $null)
            {
                Write-Host "Starting VM:", $VM.ElementName
                #Boot the VM before the script loops and moves to the next system.
                $result = $VM.requeststatechange(2)
            }
    }
}
Shrout1
  • 343
  • 2
  • 6
  • 18
  • You transcribed that whole thing by hand?! – pk. Jun 12 '14 at 18:20
  • Lol yes I did. I tend to reference my stack answers at later dates and I especially didn't want to lose this one to my black hole of a corporate network :) – Shrout1 Jun 12 '14 at 21:50