PowerShell: Monitoring DrainStatus of a Hyper-V Host & The Time Limited Value of Information In Beta & RC Era Blogs


I was writing some small PowerShell scripts to kick pause and resume Hyper-V cluster hosts and I wanted to monitor the progress of draining the virtual machines of the node when pausing it. I found this nice blog about Draining Nodes for Planned Maintenance with Windows Server 2012 discussing this subject and providing us with the properties to do just that.

It seems we have two common properties at our disposal: NodeDrainStatus and NodeDrainTarget.

image

So I set to work but I just didn’t manage to get those properties to be read. It was like they didn’t exist. So I pinged Jeff Wouters who happens to use PowerShell for just about anything and asked him if it was me being stupid and missing the obvious. Well it turned out to be missing the obvious for sure as those properties do no exist. Jeff told me to double check using:

Get-ClusterNode MyNode -cluster MyCluster | Select-Object -Property *

Guess what, it’s not NodeDrainStatus and NodeDrainTarget but DrainStatus and DrainTarget.

image

What put me off here was the following example in the same blog post:

Get-ClusterResourceType "Virtual Machine" | Get-ClusterParameter NodeDrainMoveTypeThreshold

That should have been a dead give away. As we’ve been using MoveTypeTresHold a lot the recent months and there is no NodeDrain in that value either. But it just didn’t register. By the way you don’t need to create the property either is exists. I guess this code was valid with some version (Beta?) but not anymore. You can just get en set the property like this

Get-ClusterResourceType “Virtual Machine” -Cluster MyCluster | Get-ClusterParameter MoveTypeThreshold

Get-ClusterResourceType “Virtual Machine” -Cluster MyCluster | Set-ClusterParameter MoveTypeThreshold 2000

So lessons learned. Trust but verify Smile.  Don’t forget that a lot of things in IT have a time limited value. Make sure that to look at the date of what you’re reading and about what pre RTM version of the product the information is relevant to.

To conclude here’s the PowerShell snippet I used to monitor the draining process.


Suspend-clusternode –Name crusader -Cluster warrior -Drain

Do
{
    Write-Host (get-clusternode –Name “crusader” -Cluster warrior).DrainStatus -ForegroundColor Magenta    
    Sleep 1
}
until ((get-clusternode –Name “crusader” -Cluster warrior).DrainStatus -ne "InProgress")

If ((get-clusternode –Name “crusader” -Cluster warrior).DrainStatus -eq "Completed")
{
    Write-Host (get-clusternode –Name “crusader” -Cluster warrior).DrainStatus -ForegroundColor Green
}

Which outputs

image

Monitoring Startup,Shutdown and restart of a Virtual machine With PowerShell 3.0


During scripting some maintenance PowerShell scripts for Hyper-V guests I felt the need for a more accurate way to monitor the startup of a virtual machine. Pings, telnet to a known open port it all doesn’t do the job accurately enough as I want to know when CTRL+AL+DEL appears on the screen. So I pinged Jeff Wouters who told me I could monitor Get-VM -Name DC01 | Get-VMIntegrationService  to detect when PrimaryStatusDescription goes to “OK”.

Now when you look at the Integration services there are 5 of them.

image

Which one is the best to use for our purpose? Well,I tested them out and after some experimenting with the various services I concluded that the PrimaryStatusDescription of the  Key-Value Pair Exchange works best for this purpose. All others become available a bit to soon in the process of starting a VM, which seems logical.

Monitor a starting virtual machine

So how to use this in a script? We’ll here’s a snippet to monitor the boot process of a guest.

$Vm = Get-VM "MyVM"
start-VM "$Vm"
#This means the VM is now shutting down ...    
$Counter = 0
$ProgressCount = 0
Do
{    
    $Operational = Get-VM -Name $VM | Get-VMIntegrationService -Name "Key-Value Pair Exchange"
    $Counter = $Counter + 1 
    $ProgressCount =  $ProgressCount +1
    $PercentComplete = ($ProgressCount * 20)
    Write-Progress -Activity "$VM" -status "VM starting up: $Status - Progressbar indicates activity, not a percent of completion: ($Counter Seconds)"  -percentComplete ($PercentComplete / 100 *100)
    if ($PercentComplete -gt 90) {$ProgressCount = 0}
    sleep 1
}
While ($Operational.PrimaryStatusDescription -ne "OK")
$Status = (Get-VM -Name $VM | Get-VMIntegrationService -Name "Key-Value Pair Exchange").PrimaryStatusDescription
Write-Progress -Activity "VM $VM is up and running" -status "VM status: $Status - We're done here. Completed in a total of $Counter seconds."  -percentComplete (100)

 

Monitor a stopping virtual machine

Likewise, sometime we want to monitor a VM shutting down, which is the same code as above but with reverse logic.

$Vm = Get-VM "MyVM"
stop-VM "$Vm"
$Counter = 0
$ProgressCount = 0
#This means the VM is now shutting down  in the retart cycle ...    
   Do
   {            
    $Operational = Get-VM -Name $Vm | Get-VMIntegrationService -Name "Key-Value Pair Exchange"
    $Counter = $Counter + 1 
    $Status = (Get-VM -Name $Vm | Get-VMIntegrationService -Name "Key-Value Pair Exchange").PrimaryStatusDescription
    $ProgressCount =  $ProgressCount + 1
    $PercentComplete = ($ProgressCount * 20)
    Write-Progress -Activity "$VM" -status "VM shutting down : $Status - Progressbar indicates activity, not a percent of completion: ($Counter Seconds)"  -percentComplete ($PercentComplete / 100 *100)
    if ($PercentComplete -gt 90) {$ProgressCount = 0}
    sleep 1
   }
   While ($Operational.PrimaryStatusDescription -eq "OK")
   $Status = (Get-VM -Name $Vm | Get-VMIntegrationService -Name "Key-Value Pair Exchange").PrimaryStatusDescription
   Write-Progress -Activity "VM $Vm has now been shutdown" -status "VM status: $Status - We're done here. Completed in a total of $Counter seconds."  -percentComplete (100)

Monitor a restarting a virtual machine.

When in a PowerShell script you want to monitor progress of a virtual machine restarting you can combine both. You monitor shutdown and you monitor startup.

$VmThatRestarts = Get-VM "MyVM"
#Restart the VM
#This means the VM is now shutting down  in the retart cycle ...
 $Counter = 0
 $ProgressCount = 0
Do
{            
    $Operational = Get-VM -Name $Vm | Get-VMIntegrationService -Name "Key-Value Pair Exchange"
    $Counter = $Counter + 1 
    $Status = (Get-VM -Name $Vm | Get-VMIntegrationService -Name "Key-Value Pair Exchange").PrimaryStatusDescription
    $ProgressCount =  $ProgressCount + 1
    $PercentComplete = ($ProgressCount * 20)
    Write-Progress -Activity "$VM" -status "VM restarting - Shutdown phase : $Status - Progressbar indicates activity, not a percent of completion: ($Counter Seconds)"  -percentComplete ($PercentComplete / 100 *100)
    if ($PercentComplete -gt 90) {$ProgressCount = 0}
    sleep 1
}
While ($Operational.PrimaryStatusDescription -eq "OK")
$Status = (Get-VM -Name $Vm | Get-VMIntegrationService -Name "Key-Value Pair Exchange").PrimaryStatusDescription
Write-Progress -Activity "VM $Vm has now been shutdown in restart cycle" -status "VM status: $Status - VM has shut down in $Counter Seconds"  -percentComplete (100)
   
#Any thing worthwhile is worth adding 1 second of waiting for good measure :-)
Sleep 1

#This means the VM is now starting  ...    
$ProgressCount = 0
Do
{    
    $Operational = Get-VM -Name $VM | Get-VMIntegrationService -Name "Key-Value Pair Exchange"
    $Counter = $Counter + 1 
    $ProgressCount =  $ProgressCount +1
    $PercentComplete = ($ProgressCount * 20)
    Write-Progress -Activity "$VM" -status "VM restarting - Startup phase: $Status - Progressbar indicates activity, not a percent of completion: ($Counter Seconds)"  -percentComplete ($PercentComplete / 100 *100)
    if ($PercentComplete -gt 90) {$ProgressCount = 0}
    sleep 1
}
While ($Operational.PrimaryStatusDescription -ne "OK")
$Status = (Get-VM -Name $VM | Get-VMIntegrationService -Name "Key-Value Pair Exchange").PrimaryStatusDescription
Write-Progress -Activity "VM $VM is up and running again" -status "VM status: $Status - We're done here. Completed in a total of $Counter seconds."  -percentComplete (100)

Note that in all the above snippets  I’ve thrown some logic in to us the progress bar as an activity bar as I know of no way to calculate real % done in a startup, shutdown, restart process. It looks something like this in ISE

image

or like this in a PowerShell prompt

image

Checking Host Integration Services Version on all Nodes of A Windows Server 2012 Hyper-V Cluster With PowerShell


It’s important to keep our Hyper-V cluster hosts and the virtual machines running on them up to date. Whilst we have great and free solutions to achieve this there are some things missing like centralized reporting on the Integration Services component version running on all of the nodes in a cluster and way to upgrade all the virtual machines to version running on the host. This post deals with the first issue.

Before we upgrade the Integration Services components on the virtual machines we always check if all nodes in the cluster are on the same version themselves. Sure this should not happen if you mange them right but my world isn’t perfect. So trust but verify.With cluster sizes now up to 64 nodes it’s ever more important to keep an eye on them. But even for smaller cluster the task of determining the Integration Services components manually via the GUI, event viewer and/or registry is rather tedious. Out of sync Integration Services components can be troublesome and cause many issues and if you have out of sync virtual machines, imagine the extra mess you’ll be in when even the cluster nodes are running different versions.

To make live easier I threw a little PowerShell script together to check the host Integration Services component version on all nodes of a Window Server 2012 Hyper-V Cluster With PowerShell. I’m far from a PowerShell guru, but you’ll see that you can do a lot of things  done even if you’re not. I’m sharing it here for you to use, adapt for your own needs and get some inspiration. It basically allows you to optionally pass an expected version of the IS components and a cluster name like this

CheckHyperVClusterHostsICVersion -Version 6.2.9200.16433 -cluster "MyClusterName"

It does the following:

  • It will list per Integration Services component version found on cluster nodes what version was found on what nodes. This gives you a nice overview. I hope this never becomes to much of a list in your clusters.
  • If you don’t specify a cluster it will try to connect to the cluster to which the host you’re running on belongs, if any.
  • If the host does not belong to a cluster it will just provide feedback on the IS version of that Hyper-V host you’re running the script on.

Here’s a screen shot of when you run this on a none clustered host, without Hyper-V installed:

image

This is the result of running it against a well maintained cluster without any parameters that has been updated with KB2770917:

image

The same but now with the expected version and cluster name passed as parameters

image

So, there you go, I hope you find it useful.

#===========================================================
# # Microsoft PowerShell Source File 
# 
# NAME:    CheckISCOnNodesOfHyperVCluster.ps1
# VERSION:    1.0.0.0

# AUTHOR:    Didier Van Hoye
# DATE :    17/11/2012
# 
# COMMENT:     This script is intended to be run 
#              against Windows Server 2012 and assumes
#            the use of PowerShell 3.0
#            The parameters are optional but if you
#            leave out some the remainder should be named.
# # =======================================================
 
cls
$ErrorActionPreference = "Stop"

 
function CheckHyperVClusterHostsICVersion
{
    Param
    (
        #Param help description
        [Version]
        $ExpectedISCVersion,
        #Param help description
        [String]
        $Cluster
    )

    Write-Host "This script will check the IS components on all nodes of a cluster." -ForegroundColor Green
 
    If ($ExpectedISCVersion) {Write-Host "You specified the expected IS component version to be $ExpectedISCVersion" -ForegroundColor Green}
    Else {Write-host "You did not specify an expected IS component version." -ForegroundColor Green}
    
    If ($Cluster)
    {
        Try
        {
            $ClusterObject= Get-Cluster -Name $Cluster
        }
        Catch
        {     
            Write-Host "We cannot contact the cluster you specified"
        }
    }
    Else
    {    
        write-Host "`n`n"
        Write-host "You did not specify a cluster to connect to. We'll use the cluster to which the node this script is running on belongs if any." -ForegroundColor Yellow
        write-Host "`n`n"
  
        Try
        {
            $ClusterObject = Get-Cluster
        }
  
        Catch
        {
            $LocalHost = $env:computername
            Write-Host
            Write-Host "The current node ($LocalHost) is not a member of a cluster. As a courtsey to you we'll check the IS components for current host" -foregroundcolor Magenta
            Write-Host
        }
 
    }
  
    If ($ClusterObject) {$ToCheck= "the nodes of cluster $ClusterObject"} Else { $ToCheck = "server $env:computername"}
 
    write-Host "Attempting to running Integration Components version check on" $ToCheck -ForegroundColor Green
    Write-Host


    If ($ClusterObject)
    {

        $ClusterNodes = Get-Clusternode -cluster $ClusterObject.Name
        
        #Declare an hashtable to hold all host/IS version values. The hosts are the key here.
        $HostISVersions = @{}
 
        foreach ($ClusterNode in $ClusterNodes)
        {
            Try
            {
                 $HostISVersions[$ClusterNode.Name]=Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestInstaller\Version" | select -ExpandProperty Microsoft-Hyper-V-Guest-Installer
            }
            Catch
            {
            Write-Host "We could not determine the version of the Integration Services on this host, probably due to this not being a Hyper-V host" -ForegroundColor Orange
            Write-Host "We'll check this for you right now" -ForegroundColor Orange
            $HyperVFeature = Get-WindowsFeature Hyper-V
            If ($HyperVFeature.Installstate -eq "Installed")
            {
              Write-Host "Hyper-V seems to be installed on this node. Something else is wrong." -ForegroundColor Red
            }
            Else
            {
                Write-Host "Hyper-V is indeed not installed on this node." -ForegroundColor Orange
            }
            }
        }
         #Use GetEnumerator or thise sorting thing doesn't work out well on an hash tabel :-)
        $UniqueIcVersions = $HostISVersions.GetEnumerator() | Sort-Object -Property Value -Unique
 
        Write-Host "We've found " $UniqueIcVersions.count "versions on the" $HostISVersions.count "nodes of your cluster" $ClusterObject.Name
 
        ForEach ($IcVersion in $UniqueIcVersions )
        {
            $Counter = 1
            $IcVersionValue = $IcVersion.value
            "IC version " + $IcVersion.value + " is found in:"
            foreach ($Key in ($HostISVersions.GetEnumerator()| Where-Object { $_.value -eq $IcVersionValue}))
            {
                "`t" + "$Counter : " + $Key.Name
                $Counter= $Counter + 1
            }
 
            If ($ExpectedISCVersion)
            {
               
                $CompareVersions = ([Version]$IcVersion.Value).CompareTo([Version]$ExpectedISCVersion)
                        
                switch ($CompareVersions)
                {
                    0 {Write-Host "This version ($IcVersionValue) is equal to the expected version ($ExpectedISCVersion)." -ForegroundColor Green}
                    1 {Write-Host "This version ($IcVersionValue) is higher than the expected version ($ExpectedISCVersion). Please ensure all hosts run the same IC version level." -ForegroundColor Yellow}
                    -1 {Write-Host "This version ($IcVersionValue) is lower than the expected version ($ExpectedISCVersion). Please ensure all hosts run the same IC version level." -ForegroundColor Red}
                }
            }

        }
    }

    Else
    {
        Try
        {
            $HostIcVersion = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestInstaller\Version" | select -ExpandProperty Microsoft-Hyper-V-Guest-Installer
            Write-Host "The IS component version on server $localhost is $HostIcVersion"
            If ($ExpectedISCVersion)
            {
               
                   $CompareVersions = ([Version]$HostIcVersion).CompareTo([Version]$ExpectedISCVersion)
                        
                switch ($CompareVersions)
                {
                0 {Write-Host "This version ($HostIcVersion) is equal to the expected version ($ExpectedISCVersion)." -ForegroundColor Green}
                1 {Write-Host "This version ($HostIcVersion) is higher than the expected version ($ExpectedISCVersion). Please check if you need to downgrade your host or if the expected version is correct." -ForegroundColor Yellow}
                -1 {Write-Host "This version ($HostIcVersion) is lower than the expected version ($ExpectedISCVersion). Please check if you need to upgrade your host or if the expected version is correct." -ForegroundColor Red}
                }
            }
        }
        Catch
        {
            Write-Host "We could not determine the version of the Integration Services on this host, probably due to this not being a Hyper-V host" -ForegroundColor yellow
            Write-Host "We'll check this for you right now" -ForegroundColor yellow
            $HyperVFeature = Get-WindowsFeature Hyper-V
            If ($HyperVFeature.Installstate -eq "Installed")
            {
                Write-Host "Hyper-V seems to be installed on this node. Something else is wrong." -ForegroundColor Red
            }
            Else
            {
                Write-Host "Hyper-V is indeed not installed on this node." -ForegroundColor yellow
            }
        }
    }
}
 
CheckHyperVClusterHostsICVersion -Version 6.2.9200.16433 -cluster "MyClusterName"

KB2770917 Updating Host & Guest Integration Services Components – Most Current Version Depends on Guest OS


As after installing http://support.microsoft.com/kb/2770917 on Windows Server 2012 Hyper-V hosts the integration services components are upgraded from 6.2.9200.16384 to 6.2.9200.16433. Windows Server 2012 guest get that same upgrade and as such also the newer integration services components. The guest with older OS version needed a different approach. So I turned to all the great PowerShell support now available for Hyper-V to automate this. Pretty pleased with the results of our adventures in PowerShell scripting I let the script go on Hyper-V cluster dedicated to test & development. As such there are some virtual machines on there running Windows 2003 SP2 (X64) and Windows XP SP3 (x86).  Guess what, after running my script and verifying the integration services version I see that those VM still report version 6.2.9200.16384 . No update. Didn’t my new scripting achievement “take” on those older guests?

So I try the install manually and this is what I get:

clip_image001

 

Why is there no upgrade for these guests?  Are they not needed or do I have an issue? So I mount the ISO and dig around in the files to find a clue in the date:

clip_image001[10]

 

It looks like there are indeed no update components in there for Windows XP/ W2K3. So then I look at the following registry key on the host where I normally use the Microsoft-Hyper-V-Guest-Installer-Win6x-Package value to find out what integration services version my hosts are running:

image

 

Bingo, there it seems indicated that we indeed need version for XP/W2K3 and version for W2K8(R2)/W2K12 and Vista/Windows 7/Windows 8. Cool, but I had to check if this was indeed as it should be and I’m happy to confirm all is well. Ben Armstrong (http://blogs.msdn.com/b/virtual_pc_guy/) confirmed that this is how it should be. There was a update needed for backup that only applied to Windows 8 / Windows Server 2012 guests.  As this fix was in a common component for Windows Server 2008 and later they all got the update. But for the older OS versions this was not the case and hence no update is need. Which is reflected in all the above. In short, this means your XP SP3 & W2K3SP2 VMs are just fine running the version of the integration services and are not in any kind of trouble.

This does leave me with an another task. I was planning to do enhancements to my script like feedback on progress, some logging, some better logic for clustered and non clustered environments, but now I have to also address this possibility and verify using the registry keys on the host which IC version I should check against per OS version. Checking against just for the one related to the host isn’t good enough Smile.

Don’t Be Afraid To Learn PowerShell, You’ll Need It & You’ll Learn To Love It!


As I started my career in IT doing data crunching, OLAP & development in VBA & VB5.00/6.0 it isn’t that surprising I’ve done most of my automation in VBScript. I’m very familiar with it and even in the Windows 2008 Core era it was very useful as we didn’t have .NET in it at that time, meaning no PowerShell (no, the unsupported hack to get it on there does not count).

The first real PowerShell use for me came with Exchange 2007. That worked out pretty well, but at the time we didn’t use it for much more than Exchange. Today, more and more I’m starting to use PowerShell versus VBScript. For one sometimes VBScript can’t get it done, it’s not being developed any more in its capabilities and two, PowerShell commandlets do pack a serious punch!

Since Windows Server 2008 R2 PowerShell has gotten better overall support and with Windows 8 it is everywhere, natively. That’s very good and it means we can do all we need to do now (well a whole lot of it) in PowerShell. And then I haven’t even mentioned the entire workflow support in Windows 8 PowerShell!  As an old VB guy I had to get over my dislike for curly braces. I also need to earn the different syntax and especially the way in which to use the programming constructs (control flow, sub routines, operators, data types).

A lot of people tend to focus on the one liners. These are great and powerful, but they often reminded me of the old discussion with C/C++ developers about code readability & maintainability.  For that purpose we don’t mind that the code is more verbose. One liners are not the goal  but they are fun. Just remember that all code, how small it may be, one day will have to be maintained. The more readable, logical and easy to understand it is, the better. The whole “self documenting” thingy Smile.  One liners do not always fit in here. But to demonstrate I have nothing against them I’ll show you a real easy example for all you out there dealing with the jump to PowerShell. If you get hooked on one liners just be sure to use them with reason and go visit the blog of Jeff Wouters , you’ll become good scripting buddies.

Let’s say in VBScript you needed to format a date in a specific way. In VBScript you have a very limited number of format option to use. So when you want something funky like “20120414” (YearMonthDay) as a date format you’d use a function that builds that string and pads the numbers with zero if needed. You can either write a generic function to handle all possible date needs or a custom/purpose built one for just the needs at hand.

Just to get the gist of this, it could look a bit like this:

WScript.echo FormatDateForMyNeed() PrivateFunction FormatDateForMyNeed () Dim sDate, sYear, sMonth, sDay sDate =Now() sYear =Year(sDate) sMonth =Month(sDate) IfLen(sMonth) =1Then sMonth ="0"& sMonth sDay =Day(sDate) IfLen (sDay) =1Then sDay ="0"& sDay FormatDateForMyNeed = sYear & sMonth & sDay End Function

Driven by “routine” & a VBScript background you could mash up some functions in PowerShell and make it a convoluted scripting exercise:

function BuildDate { $date=Get-Date $String= [string]$date.year $MONTH= [String]$date.month $String+= PadString "0"2$Month$DAY= [String]$date.day $String+= PadString "0"2$DAYReturn$String } function PadString ($PadChar, $PaddedLength, [String]$StringToPad) { $StringToPad= ($padChar* ($PaddedLength-$stringToPad.length)) +$stringToPadReturn$StringToPad } BuildDate

But PowerShell has way better date format support than VBScript and you can just write this:

Get-Date -format "yyyyMMdd"

Now that’s a one liner I have nothing against and yes, it saves a whole lot of effort. Sure this is a real simple example but it proves a point. Do your self a favor, take out a couple of hours a week and dabble around in PowerShell. You’ll add a valuable time saving tool to your inventory and gain a precious skillset Smile for a bright future! Need a good example? See this blog post by Janssen Jones to see some workflow goodness and what it can do.

No Hyper-V Module for Windows PowerShell After Upgrading to Windows Server 8?


When I upgraded some of my Hyper-V hosts from Windows 2008 R2 to Windows 8 I noticed I wanted to do some experiments using the Hyper-v Module for Windows PowerShell. So the first thing I did was install the Windows PowerShell integrated Scripting Environment (ISE) via the Add Roles and Features Wizard on my client. You don’t usually install this on your servers.

ISEPosh

 

We opted to restart automatically if required, so we get a warning this server might restart.

image

 

Windows PowerShell ISE is installing

installISE2

 

We are informed of our successful installation.installISE3

That was easy and no reboot required. So we launch ISE and start testing some commands of our new Hyper-V Module. But that doesn’t do much for us. Nothing happens.get-command 1

 

So I try some more commands. But no luck, just some errors that the commands are not recognized or Get-Help can’t find anything of that command.  I also not that for non of the Hyper-V commands I have any IntelliSense support.

Nope2 

No jio

So it seams the Hyper-V Module for Windows PowerShell is not installed. But I can’t make that out from the Roles Wizard.

I needed to get this going fast so I uninstalled the Hyper-V role and than added it again. That did the trick as now the Hyper-V Module for Windows PowerShell is also installed because I can execute commands

Success.

 

Just install the Hyper-V Module for Windows PowerShell via Features

But after discussing this with Microsoft it turns out that uninstalling and reinstalling the Hyper-V role is not necessary at all. You see when you upgrade a Window 2008 R2 node to Windows 8 it does not install Hyper-V by default as this would change the original install base and they try not to install features you didn’t have before during an upgrade. On a clean install where you add the Hyper-V role you won’t have this issue as the Hyper-V Module for Windows PowerShell is installed by default. What confused me is that I didn’t see an option under Roles to add Role Services to roles as I was used to do in Windows 2008 R2. There is no sub tree or anything.

Hyper-VRoleIInstalled

 

I was thinking along the same path in Windows 8 but here we can find it in the in “Add Roles and Features Wizard” under Features / Remote Server Administration Tools sub tree. That has two entries. One for Feature Administration Tools and one for Role Administration tools and und the latter we find the Hyper-V Management Tools with Hyper-V Module for Windows PowerShell. Just a tip Smile

You can add it your self after the upgrade by going to Server Manager and starting the Add Roles & Features Wizard.

image

You go through the normal steps and select to install Hyper-V Module for Windows PowerShell.

clip_image002

 

We are asked for confirmation of our request actions.

installit

 

The Hyper-V Module for Windows PowerShell is being installed.

installit2

 

And we have a successful install. We can start scripting on that node right way Smile

Installit3

Happy scripting!

Some Feedback On How to defrag a Hyper-V R2 Cluster Shared Volume


Hans Vredevoort posted a nice blog entry recently on the defragmentation of Clustered Shared Volumes and asked for some feedback & experiences on this subject. He describes the process used and steps taken to defrag your CSV storage and notes that there may be third party products that can handle this automatically. Well yes, there are. Two of the most know defragmentation products support Cluster Shared Volumes and automate the process described by Hans in his blog.  Calvin made a very useful suggestion to use Redirected Access instead of Maintenance mode. This is what the commercial tools like Raxco PerfectDisk and Diskeeper also do.

As the defragmentation of Cluster Shared Volumes requires them to be put into Redirected Access you should not have “always on” defragmentation running in a clustered Hyper-V node. Sure the software will take care of it all for you but the performance hit is there and is considerable. I might just use this point here as yet another plug for 10 Gbps networks for CSV Smile. Also note that the defragmentation has to run on the current owner or coordinator node. Intelligent defragmentation software should know what node to run the defrag on, move the ownership to the desired node that is running the defragmentation or just runs it on all nodes and skips the CSVs storage it isn’t the coordinator for. The latter isn’t that intelligent. John Savill did a great blog post on this before Windows 2008 R2 went RTM for Windows IT Pro Magazine where he also uses PowerShell scripts to move the ownership of the storage to the node where he’ll perform the defragmentation and retrieves the GUID of the disk to use with the  defrag command. You can read his blog post here and see how our lives have improved with the commands he mentions would be available in the RTM version of W2K8R2 (Repair-ClusterSharedVolume  with –defrag option).

For more information on Raxco PerfectDisk you can take a look at the Raxco support article, but the information is rather limited. You can also find some more information from Diskeeper on this subject here.  I would like to add that you should use defragmentation intelligently and not blindly. Do it with a purpose and in a well thought out manner to reap the benefits. Don’t just do it out of habit because you used to do it in DOS back in the day Smile.

To conclude I’ll leave you with some screenshots from my lab, take during the defragmentation of a Hyper-V cluster node.

As you can see the CSV storage is put into redirected access:

0

 

And our machines remain on line and  available:

1

 

This is because we started to defrag it on the Hyper-V cluster node:

2

 

Here you can see that the guest files are indeed being defragmented, in this case the VHD for the guest server Columbia (red circle at bottom):

image

Key Value Pair Exchange WMI Component Property GuestIntrinsicExchangeItems & Assumptions


Now that Windows 2008 R2 SP1 is being deployed some scripts to check whether the Integration Components (IC) in Hyper-V VM guests are upgraded came back on the radar screen. Host are being upgraded and thus the clients need upgraded IC as well. Now to check this for hundreds or thousands of guest we need some automation. PowerShell comes in handy for this and some neat scripts can be found around the internet. The most concise PowerShell code to do this, that I know of, is the one Peter Noorderijk (great Dutch IT Pro)  uses in his  PowerShell function Get-IntegrationServicesVersion on his blog How to check the version of the Integration Components.  As he provided this script just when I needed one I used it. This worked fine until I ran into an issue with it on some clusters. On two test cluster and two production clusters it did the job as expected. On one test cluster and one production cluster we ran into the situation where the output seemed wrong. The screenshot below is an example of this.

image

The red arrows indicate wrong data for the VMname and ICVersion. What happened here?  Well when we read out the  GuestIntrinsicExchangeItems property from the WMI object Msvm_KvpExchangeComponent we get back XML. That XML needs to be parsed to display it for human consumption. The  function depends on fixed positions containing the correct data. I’ve marked the relevant portions with a red arrow above, they come from$vmkvp[0] en $vmkvp[14] in the script below.

function Get-Integ.rationServicesVersion ($HVhost = $(throw “HVHost required”))
    {
    $kvps = Get-WmiObject -Namespace root\virtualization -ComputerName $HVHost -Query “Select GuestIntrinsicExchangeItems From Msvm_KvpExchangeComponent”
        foreach ($kvp in $kvps)
         {
         $vmkvp = $Kvp.GuestIntrinsicExchangeItems
         $vmkvp | select-object @{Label=”VMHost”;Expression={$hvhost}},
                                @{Label=”VMName”;Expression={([xml]$vmkvp[0]).instance.property[1].value}},        
                                @{Label=”ICVersion”;Expression={([xml]$vmkvp[14]).instance.property[1].value}} -first 1
         }    
    }
  
foreach ($hvhost in get-content servers.txt) {Get-IntegrationServicesVersion $hvhost}

And indeed, when we dump the XML for two of the affected servers out to text files you can see the order is indeed different so counting on the exact location in an array is what tripped us up here.

image

Should this ever happen? Am I making a scripting mistake somewhere? Running a check with a VBScript that parses the XML  using XDOM (just in case my PowerShell skills are the cause of this) confirms the order is different but that the key pairs match up and are correct

D:\SysAdmin\PowerShellScripts>cscript.exe test.vbs

Microsoft (R) Windows Script Host Version 5.8

Copyright (C) Microsoft Corporation. All rights reserved.

Guest OS information for server01

CSDVersion : Service Pack 1

FullyQualifiedDomainName : server01.lab.test

IntegrationServicesVersion : 6.1.7601.17514

NetworkAddressIPv4 : 10.10.100.118

NetworkAddressIPv6 : fe80::a177:729:8840:250%9

OSBuildNumber : 7601

OSEditionId : 7

OSMajorVersion : 6

OSMinorVersion : 1

OSName : Windows Server 2008 R2 Standard

OSPlatformId : 2

OSVersion : 6.1.7601

ProcessorArchitecture : 9

ProductType : 3

RDPAddressIPv4 : 10.10.100.118

RDPAddressIPv6 : fe80::a177:729:8840:250%9

ServicePackMajor : 1

ServicePackMinor : 0

SuiteMask : 272

D:\SysAdmin\PowerShellScripts>cscript.exe test.vbs

Microsoft (R) Windows Script Host Version 5.8

Copyright (C) Microsoft Corporation. All rights reserved.

Guest OS information for server13

FullyQualifiedDomainName : server13.lab.test

OSName : Windows Server 2008 R2 Standard

OSVersion : 6.1.7601

CSDVersion : Service Pack 1

OSMajorVersion : 6

OSMinorVersion : 1

OSBuildNumber : 7601

OSPlatformId : 2

ServicePackMajor : 1

ServicePackMinor : 0

SuiteMask : 272

ProductType : 3

OSEditionId : 7

ProcessorArchitecture : 9

IntegrationServicesVersion : 6.1.7601.17514

NetworkAddressIPv4 : 10.10.100.112

NetworkAddressIPv6 : fe80::c18b:e3f2:7f05:31e4%12

RDPAddressIPv4 : 10.10.100.112

RDPAddressIPv6 : fe80::c18b:e3f2:7f05:31e4%12

When I look at where that data lives in the registry on those servers it all looks exactly the same, neatly ordered buy the regedit GUI:

image
image

So when getting that data from the Key Value Pair Exchange WMI component with the property GuestIntrinsicExchangeItems you get a bunch of XML. That has to be parsed to be displayed in a readable fashion. The problem we are seeing is due to the fact that the items in the XML file are not in the same order. Peter’s function assumes it is. However this does not happen to be the case for most virtual machines, the majority is in the expected order. I don’t know why that is or if this is supposed to happen but it doesn’t seem to cause any harm. All is fully functional and operational in Hyper-V Manager, SCVMM 2008R2 … etc. Perhaps an MVP or Microsoft guru can shed some light on this. It seems like a bug waiting to happen if a developer of Hyper-V management software makes the same assumption. Of is this never suppose to happen and do I need to worry? I don’t know Smile I reinstalled the IC on the guests that have a different ordering and live migrated them, but that didn’t change anything

Anyway if you want to make sure you get the correct output we’ll need another approach that doesn’t make assumptions.You can roll your own and get the output customized to your needs but you need to parse the XML using a filter.  An example of which is listed below.

# Filter for parsing XML data
filter Import-CimXml 
{    
# Create new XML object from input  
$CimXml = [Xml]$_    
$CimObj = New-Object -TypeName System.Object     

# Iterate over the data and pull out just the value name and data for each entry   
foreach ($CimProperty in $CimXml.SelectNodes("/INSTANCE/PROPERTY[@NAME='Name']"))      
 {          
     $CimObj | Add-Member -MemberType NoteProperty -Name $CimProperty.NAME -Value $CimProperty.VALUE       
 }  
    
 foreach ($CimProperty in $CimXml.SelectNodes("/INSTANCE/PROPERTY[@NAME='Data']"))     
   
 {         
     $CimObj | Add-Member -MemberType NoteProperty -Name $CimProperty.NAME -Value $CimProperty.VALUE      
 }    


# you send the output from the property to the filter via a pipe
$KvpComponentVMGuest.GuestIntrinsicExchangeItems $vmkvp |Import-CimXml 

Maarten Wijsman (a fellow blogger at http://www.hyper-v.nu like Peter)  has a nice example script here that also uses a filter Import-CimXML. Do note that there are  variants on this filter depending on what output you desire that explains the difference between the filters.

Calling x64 CLI Tools in x86 Scripting Tools and Processes


Every now and then I get the same question from people who only recently decided to make the switch to x64 bit Windows operating systems. I’ve been running on x64 since Vista RTM and I’m very happy with it. When those people start scripting with their tools, which are 32 bit, calling some CLI tool in %windir%\System32 they can run into an annoying issue that express itself in the correct yet somewhat misleading “WshShell.Exec: The system cannot find the file specified.”. But you know it’s there in %windir%\System32, you checked and double checked!

When your scripting tool is 32 bit and you run your script it usually launches an 32 bit version of the CLI tool you’re calling. This behavior is a result of file redirection. This is a transparent process that’s part of the Windows-on-Windows 64-bit (WOW64) subsystem that is used to run 32 bit apps. When a 32 bit applications calls a CLI tool in the %windir%\system32 directory it silently redirects this to the %windir%\SysWOW64 where 32 bit apps can happily run without a worry on an x64 bit operating system. Yes, indeed %windir%\system32 is for x64 code only and %windir%\SysWOW64 is for 32 bit code.

What’s in a name :-) Some people argue they should have use system32 for 32 bit and system64 for x64 bit but I’m sure they had their reasons for what they did (i.e. it would have been hell for some reason I guess). Other suggestions have also been made by people who are far better qualified than I am. For example by Mark Russinovich, a hard core systems developer, in http://blogs.technet.com/b/markrussinovich/archive/2005/05/07/running-everyday-on-64-bit-windows.aspx.

Now all this can happen transparently for the user if the tools used have both an x64 and a x86 version. Cmd.exe and ping.exe are fine examples. If you run some VBScript in my favorite scripting tool for example (Sapiens PrimalScript) which is 32 bit it will launch a 32 bit cmd.exe, that launches the cscript.exe 32 bit version and which will launch ping.exe (using WScript.Shell) in %windir%\SysWOW64 by silently redirecting your %windir%\system32 path. No worries, you don’t know any better and the result is the same. So it’s usually not a problem if there is both a x64 and a x86 version to the CLI tool as you have seen in the ping.exe example. When a 32 bit process calls a tool in %windir%\system32 it’s redirected to %windir%\SysWOW64 and uses the 32 bit version. No harm done.

The proverbial shit hits the fan when you call a CLI tool that only has a x64 bit version. As the scripting tool is x86 it’s call is redirected to the WOW64 and the script fails miserably as the CLI tool can’t be found. This can be pretty annoying when writing and testing scripts. The CLI backup tool of Windows Backup is a prime example. It does not have a 32 bit version. Consider this little script for example:

Option Explicit

Dim oShell
Dim oExecShell
Dim sBackupCommandString
Dim sText

Set oShell = CreateObject("WScript.Shell")
'sBackupCommandString = "%windir%\sysnative\wbadmin get disks"
sBackupCommandString = "%windir%\system32\wbadmin get disks"

Set  oExecShell = oShell.Exec(sBackupCommandString)

Do While oExecShell.Status = 0
    Do While Not oExecShell.StdOut.AtEndOfStream
        sText = oExecShell.StdOut.ReadLine()
        Wscript.Echo sText 
    Loop    
Loop

Set oShell = Nothing
Set oExecShell = Nothing

There is a lot of File Redirection going on here to %windir%\SysWOW64 when running this code in the 32 bit scripting tool. That tool launches the 32 bit cmd.exe and thus the 32 bit cscript.exe which then launches a 32 bit shell and tries to run "%windir%\system32\wbadmin get disks" which is also redirected to %windir%\SysWOW64 where wbadmin cannot be found throwing the error: “WshShell.Exec: The system cannot find the file specified.”. If you don’t have a 32 bit code editor just launch the script manually from an 32 bit command prompt to see the error.

The solution as demonstrated here is to use as in “%windir%\Sysnative\wbadmin.exe get disks”. Uncomment that line and put the line with sBackupCommandString = "%windir%\system32\wbadmin get disks" in comment. Do the same test again and voila. It runs. So there you have it, you can easily test your script now. Just make sure that when the time comes to put it out in the wild you replace it with the real path if the calling process is x64 bit, which for example wscript.exe and cscript.exe are when you launch the form a x64 bit shell (explorer.exe or cmd.exe), which is the default on a x64 operating system. The x86 version runs when you launch them from a x86 shell. But remember the default on x64 bit operating systems is x64 bit and sysnative only functions when called from a 32 bit process (it’s a virtual directory that doesn’t really exists).

Sysnative was introduced in Vista/Windows2008 x64 bit. Not only 32 bit script editor users a affected by this, all 32 bit processes launching tools in "%windir%\system32 are. See more on MSDN via this link http://msdn.microsoft.com/en-us/library/aa384187(VS.85).aspx.  For the folks running XP or Windows 2003 x64 bit it is perhaps time you consider upgrading to Windows 2008 R2 or v7 x64 bit? If you can’t, no need to worry, you’re in luck. Microsoft did create a hot fix for you (http://support.microsoft.com/?scid=kb;en-us;942589) that introduces sysnative on those platforms. So welcome to the x64 bit universe, beware of file redirection in WOW64 and happy scripting :-)

Setting Dates on Folders With PowerShell


A friend of mine with a Business Intelligence company asked me a favor. They have a lot of data (files & folders) that have to be copied around in the lab, at clients etc. This often leaves the date modified on the folders not reflecting the last modified date of the most recent modification in that folder’s sub structure.  This causes a lot of confusion in their processes, communication and testing.

The needed a script to correct that. Now they wanted a script, not an application (no installations, editable code). Good news they had a Windows machine (XP or higher) to run the code on and file sharing on Linux was using SAMBA so we could use PowerShell. VBScript/Java Script can only change dates on files using the Shell.Application object but NOT of folders. They also can’t directly call Windows API’s. First of all that’s “unmanaged code to the extreme” and using a  COM dll to get access to the Windows API violates the condition set out from the start.  But luckily PowerShell came to the rescue!

To accomplish the request we sort of needed to walk the tree backwards from all it’s branches back to the root. I’m no math guru so writing that sort of a reverse incursive algorithm wasn’t really an option. I decided to use plain good old recursion and count the depth of the folder structure to know how many times I needed to recursively parse through to get the correct modified date to “walk up” the folder structure. Here a snippet as a demo:


# Demo snippet

$root = "E:\TestRoot\TestDataStructure" # The folder structure to parse
$DeepestLevel = 0 # A counter to persist the deepest level found up to that moment
$File
$LevelCheck
$Return

#Loop through the folder structure recursively to determine the deepest level.
foreach ($folder in Get-ChildItem $root -recurse | Where-Object {$_.PsIsContainer -eq "True"})

    {
        $search = $folder.FullName
        Write-Host "Folder: $search"
        #Sort the returned objects by modified date and select the most recent (last) one
  $Return = Get-ChildItem $search | Sort-Object LastWriteTime | Select-Object -last 1
  Write-Host "Childe File/Subfolder most recently modified: $Return"
        #Check how deep is the current level
  $LevelCheck = $Return.FullName.split("\").Count -1
  # Compare above with deepest level foudn so far and set to new value if needed.
  if ($LevelCheck -gt $DeepestLevel) {$DeepestLevel = $LevelCheck}
  Write-Host "LevelCheck: $LevelCheck"
  Write-Host "DeepestLevel: $DeepestLevel"

    }
# Now actually recurively walk the folder structure x times where x = Deepestlevel
do {
  foreach ($folder in Get-ChildItem $root -recurse | Where-Object {$_.PsIsContainer -eq "True"})
        {
            $search = $folder.FullName
             #Sort the returned objects by modified date and select the most recent (last) one
  $Return = Get-ChildItem $search | Sort-Object LastWriteTime | Select-Object -last 1
            Write-Host "Child File or Folder most recently modified: " $Return.Fullname
            #Set the modified date on the parent folder to the one of most recent modified child object
  if ($Return -ne $null) {$folder.LastWriteTime = $Return.LastWriteTime}
            Write-Host "Parent folder " $search " last modified date set to " $Return.LastWriteTime
        }
  ; $DeepestLevel-- } #Counter -1
until ($DeepestLevel -eq 0)

Going through the folder structure to0 often is OK, going through it to0 few times is bad as it doesn’t accomplish the goal. So the logical bug in the code that loops once to much due to “\\” in the UNC path isn’t an issue. Not really elegant but very effective. The speed is also acceptable. It ran through 30,000 files, 20 GB in all in about a minute. Quick & Dirty does the trick sometimes.

The code will work with PowerShell 1.0/2.0 against a local and a UNC path as long as you have the correct permissions.

This is just a code snippet, not the production code with error handling, so please test it in a lab & understand what it does before letting it rip through your folder structures.

Cheers