Tuesday, May 21, 2013

Enabling Network Virtualization using SCVMM Run Script Command

So, if you are in the Fabric node of SCVMM 2012 SP1 you might happen to right click on a Hyper-V Server and notice the option “Run Script Command”.

This is great, it lest me push commands out to my Hyper-V Servers and run them, just like I do with Application Scripts in Service Templates.

I recently ran into a situation where I needed to enable the Network Virtualization binding on all of my Hyper-V Servers (seven of them).  And I had no desire to open a PSSession to each and run my script block that enables it.

Since I discovered this Script Command option in SCVMM, why not use it, I am here.

Like any good SCVMM admin I defined the command and then looked at the PowerShell script behind it.

(The big problem here is that I don’t see a way to save these Script Commands and run them again.  Big fail there.)

I then took that script and made it a bit more useful to me by getting all of my Hyper-V hosts and running my command on all of them at the same time.

And I figured that ‘why not’ this is the one step that all the SCVMM Network Virtualization setup is missing.  Frankly, I would expect SCVMM just to do this for me, but alas SP1 does not.

Here is my execution:

import-module virtualmachinemanager

$scriptSetting = New-SCScriptCommandSetting

Set-SCScriptCommandSetting -ScriptCommandSetting $scriptSetting -WorkingDirectory "" -PersistStandardOutputPath "" -PersistStandardErrorPath "" -MatchStandardOutput "" -MatchStandardError ".+" -MatchExitCode "[1-9][0-9]*" -FailOnMatch -RestartOnRetry $false -AlwaysReboot $false -MatchRebootExitCode "" -RestartScriptOnExitCodeReboot $false

$RunAsAccount = Get-SCRunAsAccount -Name "DomainAdmin"

$VMHosts = Get-SCVMHost | where { $_.VirtualizationPlatform -eq "HyperV" }

$scriptBlock = {

   $vSwitch = Get-VMSwitch -SwitchType External

   ForEach-Object -InputObject $vSwitch {

      if ((Get-NetAdapterBinding -ComponentID "ms_netwnv" -InterfaceDescription $_.NetAdapterInterfaceDescription).Enabled -eq $false){

         Enable-NetAdapterBinding -InterfaceDescription $_.NetAdapterInterfaceDescription -ComponentID "ms_netwnv"

      }

   }

}

ForEach ($VMHost in $VMHosts) {

   Invoke-SCScriptCommand -Executable "%WINDIR%\System32\WindowsPowerShell\v1.0\PowerShell.exe" -TimeoutSeconds 120 -CommandParameters "-ExecutionPolicy RemoteSigned -command $scriptBlock" -VMHost $VMHost -ScriptCommandSetting $scriptSetting -RunAsAccount $RunAsAccount

}

Be sure to have the SCVMM Console installed, and modify your RunAs account name.

That Set-SCScriptCommandSetting line is all about the error handling for the script.  The only non-default setting I have in here is making sure that my hypervisors were not rebooted, no matter what happened.

Tuesday, May 7, 2013

The IP the NIC on a particular domain or address space

I have this VM.  This VM has multiple interfaces.  One interface is _the_ management interface and it is the one that the VM used when it joined my domain.

I want to discover this NIC.  I then want its IP address so I can embed that into a configuration file.

The initial list of questions that I began with were:

  • Am I domain joined?  If so, what domain?
  • What network connection reports that domain?
  • What NIC is that?
  • What is the IPv4 address on that NIC?

I have discovered that there are multiple ways to handle this.

First of all, asking the questions; Am I joined and what domain am I joined to.

$env:USERDNSDOMAIN or Get-WmiObject -Class Win32_ComputerSystem | select domain

Both give you results, but different objects back.

BRIANEH.LOCAL (a string) vs.

domain                (a portion of a WMI object)                                                                                                
------

brianeh.local

Now, lets look at the question of what NIC is on what domain or connected to any domain.  If you only have one NIC connected to any domain (or that can resolve any domain) you can probably simplify this to:

Get-NetConnectionProfile | where {$_.NetworkCategory -eq "DomainAuthenticated" }

The alternate to that is to be more verbose (or precise if you like).

Get-NetConnectionProfile | where { $_.Name -eq $env:USERDNSDOMAIN }

What you get back is a Connection Profile object.  And to some this looks familiar, you know that status that you see on the network icon in your system tray?  The one that says if you have internet connectivity, or what DNS domain is discovered?  This is the information behind that.

I have two and they look like this:

Name             : Unidentified network
InterfaceAlias   : Ethernet 2
InterfaceIndex   : 13
NetworkCategory  : Public
IPv4Connectivity : LocalNetwork
IPv6Connectivity : LocalNetwork

Name             : brianeh.local
InterfaceAlias   : Ethernet
InterfaceIndex   : 12
NetworkCategory  : DomainAuthenticated
IPv4Connectivity : Internet
IPv6Connectivity : LocalNetwork

In the example I went straight to the selection of the desired one.

Now, how do I get to the IP you might wonder.  One more step.

Get-NetIPAddress -InterfaceIndex $netProfile.InterfaceIndex -AddressFamily IPv4

Again, I went straight to the IPv4 filter.  I used the Network Profile object captured as $netProfile and its InterfaceIndex property.  But, as you play with the Network IP Address object you will see that it can be selected multiple ways.

And if you only want the IP address itself, take that Network IP Address object and select only the IPv4Address property.

$netIpAddress.IPv4Address

And there you have it.  Now, off to building my configuration file…

Wednesday, May 1, 2013

Discovering programs on a disk to run silent installers

I recently worked through my version of a PowerShell snip that has to discover in installer and run it.

In my case that installer is on an ISO that is attached to a VM.  And my script runs within the VM.

Now, this should be easy, just find the DVD drive, and run the installer.

Well, as always happens, it is not that easy in my case.  Come to find out, I could have multiple ISO attached at the moment my script is running, and I need to detect the correct one.  I also have to make sure that the installer exists before I try to run it and cause an error.  (there is always a need for error handling).

If I didn’t have this mess I could just assume that my ISO is D:\ and put in the path and move on.

First, ask yourself if the ISO will ALWAYS be on D:\?  If not, then you need to find the DVD drives and specifically those that have something mounted.  From within the running OS, not at some VM management layer.

I do that with the following:

# CD-ROM selects anything in the DVD drive.  The size ensures that something is mounted.
$dvdDrives = Get-Volume | where {$_.DriveType -eq "CD-ROM" -and $_.Size -gt 0}

I also have different installers for x86 and x64, so I have to detect the ‘bit-ness’ of the OS and smartly choose the correct installer.  A bit of searching turned me to this reliable way:

Switch ([System.IntPtr]::Size)
{
    4 {
        # x86
       }
    }
    8 {
       # x64
       }    
    }
}

Now, I have to build the path so that later on in my script I can run it.

If (Test-Path -Path ($dvd.DriveLetter + ":\Installer\") -PathType Container){
    $InstallMedia = Get-ChildItem -Path ($dvd.DriveLetter + ":\Installer\") -recurse -Filter "ServerInstaller.exe"
}

And, before attempting to run it, I need to test that the file really exists. And what is the literal path to the installer. Because how I got here was by testing for the folder structure, not by searching for the individual files (which would take a lot longer).

If ($InstallMedia -ne $null){
    If (Test-Path $InstallMedia .FullName){
        $InstallPath = $InstallMedia .FullName
    }
}

The entire script:

# CD-ROM selects anything in the DVD drive.  The size ensures that something is mounted.
$dvdDrives = Get-Volume | where {$_.DriveType -eq "CD-ROM" -and $_.Size -gt 0}

# Since a VM could have more than one DVD drive, we need to find the correct one.
foreach ($dvd in $dvdDrives){
    Switch ([System.IntPtr]::Size)
    {
        4 {
            If (Test-Path -Path ($dvd.DriveLetter + ":\x86\") -PathType Container){
                $Media = Get-ChildItem -Path ($dvd.DriveLetter + ":\x86\") -recurse -Filter "ServerSetup.exe"
            }
        }
        {
            If (Test-Path -Path ($dvd.DriveLetter + ":\x64\") -PathType Container){
                $Media = Get-ChildItem -Path ($dvd.DriveLetter + ":\x64\") -recurse -Filter "ServerSetup.exe"
            }    
        }
    }

    If ($Media -ne $null){
        If (Test-Path $Media.FullName){
            $FilePath = $Media.FullName
        }
    }
}

Start-Process -FilePath $FilePath –ArgumentList “/quiet “ -Wait -NoNewWindow
"Done waiting for the installer"