Não é possível desligar a VM do HyperV com o Powershell apesar dos direitos de administrador

2

Estou tentando usar um script que desliga minhas VMs do Hyper-V, copia seus VHDs para um compartilhamento de destino e, em seguida, inicializa-os novamente. Eu encontrei o script no spiceworks apenas alguns dias atrás. Eu sempre recebo erros que o nome do sistema destinado ao desligamento é um valor nulo.

Informações gerais:

  • Estou usando o powershell como administrador.
  • Eu configurei minha ExecutionPolicy como "RemoteSigned" para que os arquivos locais do PowerShell sejam executados.
  • Parece que estou executando o PowerShell v 2.0
  • Estou executando o HyperV no Server 2008 R2
  • Estou executando esse script diretamente no host da VM

Abaixo está o script e uma explicação de onde eu encontrei o erro e exatamente o que esse erro é:

 $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
 }

O script aceita o argumento "1" ou "0" para determinar se as VMs na lista de "convidados" devem ser encerradas ou iniciadas.

Rodando o Powershell como administrador, posso executar com sucesso a seguinte consulta:

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

Isso retorna uma string de algum tipo que representa o nome do sistema da VM.

No entanto, a seguinte consulta sempre retorna um valor nulo:

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

Parece que a classe 'Msvm_ShutdownComponent' não existe no meu sistema ... quando executo a seguinte linha:

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

Eu sempre recebo um erro que declara "Você não pode chamar um método em uma expressão com valor nulo". Eu passei cerca de um dia e meio tentando encontrar o erro que estou cometendo que causa isso, mas não posso reduzi-lo.

    
por Shrout1 25.03.2014 / 21:49

1 resposta

0

Eu criei um script que atende às minhas necessidades e publicarei aqui.

Este script realiza as seguintes tarefas:

  • Itera em todos os hosts físicos listados
  • Itera em todas as VMs não excluídas
  • Encerra as VMs e & aguarda o desligamento para concluir
  • Executa a transferência síncrona do BITS de todos os arquivos VHD (um de cada vez) para as VMs de destino para o local especificado
  • Boots segmentam VM após a conclusão da transferência

Esse script pode operar em hosts físicos remotos e não precisa ser executado localmente. Certifique-se de executar o script com credenciais administrativas ou não funcionará corretamente.

Estou usando o Windows Server 2008 R2 e, portanto, não tenho acesso a todos os comandos sofisticados disponíveis no Windows Server 2012.

Além disso, esta é a expressão final de "script kiddie" aqui; Eu misturei cerca de 4 scripts diferentes e, em seguida, adicionei um pouco de lógica sobre ele. Perdoe as inconsistências na sintaxe, etc.

Esse script foi transcrito à mão de um sistema corporativo autônomo que eu uso, portanto, pode haver alguns erros de ortografia. Eu não tentei executar este script diretamente. O do meu sistema parece funcionar bem.

#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)
            }
    }
}
    
por 12.06.2014 / 20:16