Hyper-V 2012 R2: copy file from host into guest VM

I wanted to move some policy definitions files from 2012 R2 to a domain joined VM.

I made a zip of the missing files and directly tried to use the new Copy-VMFile cmdlet of the Hyper-V module of Windows 2012 Server R2.

I got a little bit disappointed as my first attempt ended with the following error message:

Copy-VMFile : ‘GuestVMName’ failed to copy file. (Virtual machine ID a4391904-ba6c-462f-99e0-7abd1d90b0a5)
‘PRA406’ failed to initiate copying files to the guest: The device is not ready. (0x80070015). (Virtual machine ID
a4391904-ba6c-462f-99e0-7abd1d90b0a5)
‘GuestVMName’: The ‘Guest Service Interface’ integration service is either not enabled, not running or not initialized.
(Virtual machine ID a4391904-ba6c-462f-99e0-7abd1d90b0a5)
The system cannot process the request at this time.

I thought the integration service weren’t upgraded, so I checked with the following command:

Get-VM | ? State -eq "Running" |            
Select Name,State,Integrationservicesversion |            
ft -AutoSize

If I had read more carefully the above message, I’d have found immediately that the ‘Guest Services’ weren’t enabled (confirmed by the UI)

Get-VM -Name GuestVMName |  Get-VMIntegrationService

Let’s enable it:

Get-VM -Name GuestVMName |             
Get-VMIntegrationService | ? {-not($_.Enabled)} |             
Enable-VMIntegrationService -Verbose

My second attempt with Copy-VMFile ran like a charm 😎

Copy-VMFile -Name GuestVMName -SourcePath $home\documents\policydefinitions.zip -DestinationPath C:\Users\administrator\documents -FileSource Host

How to revert virtual machines in your lab

Last week I attented a Microsoft training on Windows 8 (22688A) in a local training center.

I wrote a few lines of code at the beginning of the session to revert a specific VM to its initial state as we had to repeat this task at the end of each lab.

Get-VM -Name 22688A-LON-CL1 |
Get-VMSnapshot | Sort CreationTime |
Out-GridView -PassThru |
Restore-VMSnapshot -Confirm:$false -Passthru -Verbose |
ForEach-Object -Process {
	Start-VM -Name $_.VMName -Verbose
}


When you revert a VM to its initial state, the trust relationship with its domain is broken. The password of the computer needs just to be reset.

I didn’t use the Test-ComputerSecureChannel although it was quickly mentioned at the end of chapter 3.

Test-ComputerSecureChannel -Repair:$true

As we log on the client computer with domain admin credentials (a very bad practice), I just did:

Notice that no reboot is required 😀

Both Test-ComputerSecureChannel and Reset-ComputerMachinePassword exist in Powershell 2.0 but as of version 3.0, you’ve now the ability to specify credentials used to perform the reset. Very handy, isn’t it?

At the end of the week to revert all the virtual machines, I used the following code 😎

Get-VM | Out-GridView -PassThru -Title "Select Virtual Machines" | % {            
    $_ | Get-VMSnapshot | Sort CreationTime |             
    Out-GridView -PassThru -Title "Select one snapshot for $($_.Name)"            
} | Restore-VMSnapshot -Confirm:$false -PassThru -Verbose

When was the last time that VM was powered on?

It seems that VMWare ESX PowerCli has a nice cmdlet called Get-LastPowerOn to answer that question:
https://blogs.vmware.com/vipowershell/2009/10/when-was-the-last-time-that-vm-was-powered-on.html
and that you can smoothly do:

Get-VM | Get-LastPowerOn

This question was raised for Hyper-V in December 2012 and didn’t get a correct answer yet.
http://stackoverflow.com/questions/13873575/how-can-i-know-the-hyper-v-vms-last-poweroff-time

Even if Windows Server 2012 R2 hit GA a few days ago and has 14 new cmdlets in its Hyper-V module compared to its predecessor Server 2012…


(I’m digressing a little bit but here are the new cmdlets:

)… it doesn’t have a nice Get-VMLastPowerOn cmdlet yet 😦

No problem, Powershell to the rescue 😀

#Requires -version 3.0
Function Get-VMStateHistory {
[CmdletBinding()]
Param(
 [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
 [string[]]$ComputerName=$env:COMPUTERNAME
)
Begin {
    $HT = @{
        LogName = 'Microsoft-Windows-Hyper-V-Worker-Admin' ;
        Id = 18500,18502,18504,18508,18510,18512,18514,18516,18518,18596
    }
}
Process {
    foreach ($Computer in $ComputerName) {
        try {
            Get-WinEvent -FilterHashtable $HT -ComputerName $Computer -ErrorAction Stop | ForEach-Object {
                $obj = Switch ($_.Id) {
                    18500 { @{ Action = 'Started'             ; TurnedOn = $true } ; break}
                    18502 { @{ Action = 'Turned Off'          ; TurnedOn = $false} ; break}
                    18504 { @{ Action = 'Shutdown (by host)'  ; TurnedOn = $false} ; break}
                    18508 { @{ Action = 'Shutdown (by guest)' ; TurnedOn = $false} ; break}
                    18510 { @{ Action = 'Saved'               ; TurnedOn = $false} ; break}
                    18512 { @{ Action = 'Reset (by host)'     ; TurnedOn = $true } ; break}
                    18514 { @{ Action = 'Reset (by guest)'    ; TurnedOn = $true } ; break}
                    18516 { @{ Action = 'Paused'              ; TurnedOn = $false} ; break}
                    18518 { @{ Action = 'Resumed'             ; TurnedOn = $true } ; break}
                    18596 { @{ Action = 'Restored'            ; TurnedOn = $true } ; break}
                    default {}
                }        
                [pscustomobject]@{
                    VMName = $_.Properties[0].Value
                    TurnedOn = $obj['TurnedOn']
                    Action = $obj['Action']
                    Time = $_.TimeCreated
                }
            } 
        } catch {
            Write-Warning -Message "Failed to get events from $Computer because $($_.Exception.Message)"
        }
    }
}
End {}
} 

Function Get-VMLastPowerOn {
[CmdletBinding()]
Param(
 [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
 [alias("VMName")]
 [string[]]$Name,

 [Parameter(ValueFromPipelineByPropertyName)]
 [string[]]$ComputerName
)

Begin {
    if ($ComputerName) {
        $VMStatesEvents = Get-VMStateHistory -ComputerName $ComputerName| ? TurnedOn
    } else {
        $VMStatesEvents = Get-VMStateHistory | ? TurnedOn
    }
}
Process {
    Switch (([int]$PSBoundParameters.ContainsKey('Name'))) {
        1 {
            foreach ($VM in $Name) {
                $VMStatesEvents | ? VMName -Like $VM | Sort-Object -Descending -Property Time | Select -First 1
            }
        }
        0 {
            foreach ($VM in (($VMStatesEvents).VMName | Sort-Object -Unique)) {
                $VMStatesEvents | ? VMName -Like $VM | Sort-Object -Descending -Property Time | Select -First 1
            }
        }
    }
}
End{}
}

I wish I had more time and I known I should have written the help assiociated with the 2 cmdlets. Instead here’s how to use it:

Case 1: Get the last power on time of an specific VM passed as parameter

Get-VMLastPowerOn -Name MyVMName | ft -AutoSize

Case 2: List last power on info for all VMs on the current host

Get-VMLastPowerOn | ft -AutoSize

Case 3: Pipe the original Get-VM cmdlet of the Hyper-V module and Get-VMLastPowerOn.

Get-VM | ? Name -match "R" |             
Get-VMLastPowerOn -ComputerName localhost

Did I already say that Powershell rocks 😎

Bonus: You may have noticed I chose specific event Ids. There are more on Windows 2012 R2 (18594 = fast-saved and 18592 = fast-restored, for example)
Here’s how I made up my mind and chose the above specific Ids:

(Get-WinEvent -ListProvider Microsoft-Windows-Hyper-V-Worker).Events |            
? id -match "^185" |ft id,Description -AutoSize

Create an external VM switch in Hyper-V

This morning I wanted to quickly add an External Hyper-V switch in my lab.
I’ve already added an Internal switch but as I needed to get access to Internet, an External switch was required.

Without reading the help, I naturally did:

New-VMSwitch -Name External -SwitchType External

Even if the External value was technically allowed by tab completion, the following error reminded me how stupid was my above assumption.
New-VMSwitch : Cannot validate argument on parameter ‘SwitchType’. The argument “External” does not belong to the set
“Internal,Private” specified by the ValidateSet attribute. Supply an argument that is in the set and then try the
command again.

Of course, to get access to Internet or to the same network as the physical nic, the External switch needs to be bound to a physical network card!

To help also states:

-SwitchType
Specifies the type of the switch to be created. Allowed values are Internal and Private. To create an External
virtual switch, specify either the NetAdapterInterfaceDescription or the NetAdapterName parameter, which
implicitly set the type of the virtual switch to External.

Then I remembered that I already did this operation some months ago where I had only one physical adapter:

New-VMSwitch -Name Prod -NetAdapterName ((Get-NetAdapter|            
? Status -eq "Up")[0].Name)

I couldn’t use exactly the same above command I previously used because the firt item returned by the filtered Get-NetAdapter” cmdlet was the Internal switch I previously set.

As you can see, filtering with the ‘Status’ wasn’t enough. I decided to compare the properties between the 2 network cards instances returned by the Get-NetAdapter cmdlet.

I quickly did…

$a =  (Get-NetAdapter  | ? Status -eq "Up")[0]            
$b =  (Get-NetAdapter  | ? Status -eq "Up")[1]            
Compare-Object -ReferenceObject $a -DifferenceObject $b

…and couldn’t get the result I expected 😦

Comparing instances of WMI objects isn’t as trivial as it seems to be. Here’s the quick and dirty thing I did:

$a =  (Get-NetAdapter  | ? Status -eq "Up")[0]            
$b =  (Get-NetAdapter  | ? Status -eq "Up")[1]            
# Fill-in an array with properties that have a value            
$props = @()            
($a | gm -MemberType Property).Name | % {            
    if (($a.$_)|Out-String) {             
        $props += $_             
    }            
}            
# Compare properties            
$props | % {             
 Compare-Object -Ref $a -Diff $b -Property $_ | Out-String            
}

After that, I found the NdisPhysicalMedium that seems promising and able to differentiate the Internal switch and the physical Nic of the Hyper-V host.
I found the meaning of the different values on http://www.powershellmagazine.com/2013/04/04/pstip-detecting-wi-fi-adapters

To install my External VM switch, I just extended the filter and did:

New-VMSwitch -Name External -NetAdapterName (            
Get-NetAdapter |?{            
 $_.Status -eq "Up" -and $_.NdisPhysicalMedium -eq 14             
 }).Name

Extending a system drive volume

My System Center Configuration Manager 2012 SP1 server is a VM running on Hyper-V 3.0. I’ve started the VM with a dynamic disk of 50GB and installed almost everything in the default location. But after a few weeks, the freespace on the system partition decreased to 9GB. The ConfigMgr started logging warnings and alerts:

Basically I had 3 options: I could either

  • move some components (SQL,…) to my second drive that has 30GB freespace
  • or decrease the thresholds so that the ConfigMgr stops logging warnings
  • or extend the partition of the system drive

I chose the later because moving the SQL Database can have some undesired consequences. Although it doesn’t apply to ConfigMgr 2012 SP1, I could have had the following issue: After moving the System Center 2012 Configuration Manager SQL Site Database to another drive, creating a new Software Update package or a new application fails

Here’s what I did

  • Backup the VM
  • $BackupShare = "\\mytarget.fqdn\myBackupShare$"            
    $cred = (Get-Credential)            
    $pol = New-WBPolicy            
    Get-WBVirtualMachine | ? VMName -eq "myCM2012ServerName" | Add-WBVirtualMachine -Policy $pol            
    $targetvol = New-WBBackupTarget -NetworkPath $BackupShare -Credential $cred -NonInheritAcl:$false            
    Add-WBBackupTarget -Policy $pol -Target $targetvol            
    Set-WBSchedule -Policy $pol -Schedule ([datetime]::Now.AddMinutes(10))            
    Start-WBBackup -Policy $pol

    If you want to read more on how to backup a VM in Hyper-V 3.0 you can read my post https://p0w3rsh3ll.wordpress.com/2013/03/06/backup-hyper-v-with-powershell/

  • Disable the replication and make sure there’s no snapshot

  • Do not perform actions (resize, shrink, convert,…) on virtual hard disk associated with a virtual machine that has snapshots, has replication enabled, or associated with a chain of differencing virtual hard disks. Otherwise, data loss is likely to occur.

  • Stop the running VM because of the following warning

  • Resize-VHD is an offline operation; the virtual hard disk must not be attached when the operation is initiated.

  • Detach the virtual hard disk from the VM
  • I only have one IDE drive on my VM so I can do:

    Get-VMHardDiskDrive -VMName "myCM2012ServerName" -ControllerType IDE |             
      Remove-VMHardDiskDrive
  • Resize the disk: add 15GB more
  • Resize-VHD -Path D:\VM\HDD\myVMName_DRIVE_C.vhdx -SizeBytes 65GB
  • Attach the drive back to the VM
  • Add-VMHardDiskDrive -VMName "myCM2012ServerName" -ControllerType IDE -ControllerNumber 0 -ControllerLocation 0 -Path D:\VM\HDD\myVMName_DRIVE_C.vhdx
  • Start the VM
  • Extend the C: (system drive) inside the VM
  • Although I could have done it with the following PowerShell cmdlets,

    Resize-Partition -DiskNumber 0 –PartitionNumber 2 -Size (            
    Get-PartitionSupportedSize –DiskNumber 0 –PartitionNumber 2).SizeMax

    I did it with the classic brave old diskpart command:

    I’ve also filled-in a documentation bug as the last example of the Resize-partition uses the MaximumSize property instead of the SizeMax property returned by the Get-PartitionSupportedSize cmdlet.

  • Enable replication

Total downtime a few minutes (I didn’t count) and my freespace issue is fixed 🙂

Backup Hyper-V with Powershell

A few days ago, Ben Armstrong, the Virtual PC Guy posted on his blog the following:

Why would you backup VMs / When / What’s the best practice
Why? Just in case of a disaster, you should be able to recover your VMs.
When? The best practive analyzer has the answer:

Get-BpaModel  -ModelId Microsoft/Windows/Hyper-V | Invoke-BpaModel

Get-BpaModel | ? Name -match "Hyper-V" |            
Get-BpaResult | ?{($_.Resolution) -and ($_.Severity -eq "Error")}|            
fl -p ResultNumber,Severity,Category,Title,Problem,Resolution

How do I perform the backup with PowerShell

  • Prerequisite #1: Install the backup features
  • Get-WindowsFeature | ? { $_.DisplayName -match "Backup" }

    Add-WindowsFeature -Name Windows-Server-Backup -IncludeAllSubFeature:$true -Restart:$false

  • Prerequisite #2: Make sure the integration services have been installed
  • Get-VM | ? State -eq "Running" |            
    Select Name,State,Integrationservicesversion |            
    ft -AutoSize

    Query the VM integration services version of running VMs to prevent them going into a saved state if they don’t have the latest VM integration services installed.
    Ben Armstrong, the Virtual PC Guy in his posts also said:

    The virtual machine will only be put into a saved state if it is not running the latest virtual machine additions (or is not a Windows virtual machine).

    More info about VM integration services, see my post: https://p0w3rsh3ll.wordpress.com/2012/11/15/follow-up-about-hyper-v-vm-integration-services/

  • Check what VM can be backed up and how
  • Get-WBVirtualMachine  | ft VMName,Caption -AutoSize


    If the VM is off, the backup is performed from the saved state.
    If the VM is running, the backup is performed using a snapshot.

  • Backup a single VM manually and synchronously
  • # Create an empty policy object                        
    $pol = New-WBPolicy            
    

    # Add a single VM to the backup            
    Get-WBVirtualMachine | ? VMName -eq myVMName | Add-WBVirtualMachine -Policy $pol

    # Define a target folder for the backup            
    $targetvol = New-WBBackupTarget -NetworkPath \\mytargetserver.fqdn\HVBackup$ -Credential (Get-Credential) -NonInheritAcl:$false -Verbose            
    Add-WBBackupTarget -Policy $pol -Target $targetvol

    # Define a schedule            
    Set-WBSchedule -Policy $pol -Schedule ([datetime]::Now.AddMinutes(10))            
                
    # Start the backup synchronously            
    Start-WBBackup -Policy $pol


    Note that you cannot start a saved VM (off) when there’s a backup in progress:

    # Review the result            
    Get-WBSummary            
    Get-WBBackupSet -BackupTarget (Get-WBBackupTarget $pol)            
    Get-WBJob -Previous 1


We have the ability to add multiple VMs to a single backup job. However VMs will be backed up sequentially.
You may have also seen how tedious it can be to perform the recovery in Ben Armstrong’s blog post, so I propose the following code snippet that may help:

$BackupShare = "\\mytargetserver.fqdn\HVBackup$"
$cred = (Get-Credential)
Get-VM | ForEach-Object -Process {
    $targetfolder = Join-Path -Path $BackupShare -ChildPath "$($env:COMPUTERNAME)\$($_.Name)"
    If (Test-Path $targetfolder) {
        Write-Verbose -Message "Previous backup for VM $($_.Name) found" -Verbose
        # Delete it first
        try {
            Remove-Item -Path $targetfolder -Force -ErrorAction Stop
        } catch {
            Write-Warning -Message "Failed to remove target folder $targetfolder"
            return
        }
        # Create directory
        try {
            New-Item -Path $targetfolder -ItemType Directory -ErrorAction Stop
        } catch {
            Write-Warning -Message "Failed to create target folder $targetfolder"
            return
        }
    } else {
        # Create directory
        try {
            New-Item -Path $targetfolder -ItemType Directory -ErrorAction Stop
        } catch {
            Write-Warning -Message "Failed to create target folder $targetfolder"
            return
        }
    }

    # Create an empty policy object            
    $pol = New-WBPolicy

    # Add the VM
    Get-WBVirtualMachine | ? VMName -eq $_.Name | Add-WBVirtualMachine -Policy $pol
    
    # Define a target folder
    $targetvol = New-WBBackupTarget -NetworkPath $targetfolder -Credential $cred -NonInheritAcl:$false -Verbose
    Add-WBBackupTarget -Policy $pol -Target $targetvol

    # Set a schedule
    Set-WBSchedule -Policy $pol -Schedule ([datetime]::Now.AddMinutes(10))

    # Start the backup
    Start-WBBackup -Policy $pol -Async 

    # Let us know the status of the running job
    While((Get-WBJob).JobState -eq "Running") { 
        $percent = ([int](@(([regex]'.*\((?<percent>\d{2})\%\).*').Matches((Get-WBJob).CurrentOperation).Groups)[-1].Value))
        if ($percent) {
            Write-Progress -Activity "Backup of VM $($_.Name) in progress" -Status "Percent completed: " -PercentComplete $percent
        } else {
            Write-Progress -Activity "Backup of VM $($_.Name) in progress" -Status (Get-WBJob).CurrentOperation
        }
    }
}

Follow-up about Hyper-V VM integration services

Following my post about Hyper-V VM integration services, https://p0w3rsh3ll.wordpress.com/2012/11/12/keeping-hyper-v-vm-integration-services-up-to-date/, I’ve some additional tips to share:

  • A new version of the VM integration services has been released in November: 6.2.9200.16433
  • It was first spotted by Thomas Maurer on the following page http://www.thomasmaurer.ch/2012/11/first-patch-tuesday-with-windows-server-2012-hyper-v-patches-new-version-of-hyper-v-integration-servies/

    The new package is actually bundled in KB2770917

    I couldn’t find the package in the download center as mentioned on the KB2770917
    My WindowsUpdate.log indicates that it got it from this location

  • About identifying VM integration services version
  • You can actually get the VM integration services version only when the VM is running. So, we can just the following filter:

    Get-VM | ? State -eq "Running" |            
     Select Name,State,Integrationservicesversion |            
     ft -AutoSize

    Get VM integration services version

  • About upgrading the VM integration services
    • Load the ISO that contains the new version
    • # Load the ISO            
      Get-VM | ? State -eq "Running" |            
       ?  IntegrationServicesVersion -eq "6.2.9200.16384" |            
       Get-VMDvdDrive | Set-VMDvdDrive -Path C:\Windows\system32\vmguest.iso
    • Run the setup.exe
    • Get-VM | ? State -eq "Running" | ?  IntegrationServicesVersion -eq "6.2.9200.16384" |             
      ForEach-Object -Process {            
          Invoke-Command -ComputerName $_.VMName -ScriptBlock {            
              Get-WmiObject -Query 'Select * FROM win32_volume WHERE DriveType = 5' |            
              ForEach-Object -Process {            
                  if (Test-Path -Path "$($_.Caption)support\$env:PROCESSOR_ARCHITECTURE\setup.exe") {            
                      # 'Found'            
                      $ps = new-object System.Diagnostics.Process            
                      $ps.StartInfo.Filename = "$($_.Caption)support\$env:PROCESSOR_ARCHITECTURE\setup.exe"            
                      $ps.StartInfo.Arguments = " /quiet /norestart"            
                      $ps.StartInfo.RedirectStandardOutput = $True            
                      $ps.StartInfo.UseShellExecute = $false            
                      $ps.start()            
                      $ps.WaitForExit()            
                      'Exit code {0}' -f $ps.ExitCode            
                      Restart-Computer -Force            
                  } else {            
                      # 'Not Found'            
                  }            
              }            
          }            
      }

      Note that a reboot is required and is included in the above code.

  • About troubleshooting the VM integration services upgrade
    • using the log file
    • To troubleshoot the installation of VM integration services, you can read the C:\Windows\vmguestsetup.log located on the guest operating system.

    • using the event log
    • If the upgrade is successful, you’ll find the ‘Setup’ eventlog an entry about KB955484 (don’t be surprised but the page doesn’t exist anymore…)
      VM integration event

    • using the Deployment Image Servicing and Management tool
    • If I do:

      dism --% /online /get-packages

      We can see that:
      DISM

    Bonus: a wiki techNet article about ‘PowerShell – Running Executables’
    http://social.technet.microsoft.com/wiki/contents/articles/7703.powershell-running-executables.aspx