How PowerShell solved the Mystery of the VM Shadow Ghost

by Klaus Graefensteiner 17. November 2011 06:06

Once upon a time…

there was a Windows 2008 R2 server in the network that behaved very strangely.

 TheMisteryOfTheZombyVM

Figure 1: The ghost vm

The Windows Process Activation Service sometimes failed to start

There server would take what seems an eternity to start up. IIS wouldn’t start, because the Windows Process Activation Server is hung during startup. This behavior would come and go. Sometimes I would get emails from the past. Ping wouldn’t work reliably.

Very mysterious!

After a few month of blaming Microsoft

I had an idea. Imagine a second copy of the VM is running in the network. Would that explain the strange behavior? Especially, if the two virtual machines are on the same domain?

Hunting for the Zombie with PowerShell

First I shutdown the server and tried to ping it. Success. The zombie is alive. I don’t believe in zombies so I decided to assume that a second copy of the VM must be running on an unknown host. How would I find the host of this VM?

Fortunately each of our VMs have PowerShell Remoting enabled and are configured to use the physical CD Rom drive of the host as virtual drive in the VM. I goggled and found a PowerShell script that would eject the CD drives. I spiced the script up so it would run on a remote computer using PowerShell Remoting.

My script is based on the following example: http://powershell.com/cs/blogs/tips/archive/2009/04/24/ejecting-cds.aspx

PowerShell script to locally eject an array of CDs

Set-StrictMode -Version "Latest"
$DebugPreference = "Continue"

$Drives = 'D:' , 'E:', 'F:', 'G:'

$sa = New-Object -comObject Shell.Application
foreach( $Drive in $Drives)
{
    try
    {
        Write-Debug "Ejecting $Drive"
        $sa.Namespace(17).ParseName("$Drive").InvokeVerb("Eject")
        Write-Debug "Ejected $Drive"
    }
    catch
    {
         Write-Debug "Failed to eject $Drive"
         Write-Debug $_.Exception.Message
    }
}

PowerShell script to remotely eject an array of CDs

Set-StrictMode -Version "Latest"
$DebugPreference = "Continue"

$Config = @{}
$Config.PSRemotingCredentialPassword = "002200200200200200200202....00776655.4.....444..333..22"
$Config.PSRemotingCredentialDomain = "neexm"
$Config.PSRemotingCredentialUserName = "SmartyP"
$Config.VMComputerName = "ZombieServer"


function Create-PSRemotingSession()
{
    #The password key is only usable by the domain user who created it. 
    #Use the Create-SecretPasswordFile function to create one for your login
    $SecurePassword = ConvertTo-SecureString $Config.PSRemotingCredentialPassword 
    $DomainUser = "{0}\{1}" -f $Config.PSRemotingCredentialDomain, $Config.PSRemotingCredentialUserName
    Write-Debug $DomainUser
    $Cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $DomainUser, $SecurePassword
    $Session = New-PSSession -ComputerName $Config.VMComputerName -credential $Cred
    return $Session
}

function Get-DecryptedString($EncryptedString) 
{
    $Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($EncryptedString)
    $Result = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr)
    [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr)
    $Result
}

function Create-SecretPasswordFile([string] $PasswordFilePath)
{
    $Password = Read-Host "Enter your password" -AsSecureString
    $EncryptedPassword = ConvertFrom-SecureString $Password
    $EncryptedPassword | Out-File -FilePath $PasswordFilePath
}


function Eject-CD($Session)
{

    $LocalCDEjectorScriptString = @'
    $Drives = 'D:' , 'E:', 'F:', 'G:'

$sa = New-Object -comObject Shell.Application
foreach( $Drive in $Drives)
{
    try
    {
        Write-Debug "Ejecting $Drive"
        $sa.Namespace(17).ParseName("$Drive").InvokeVerb("Eject")
        Write-Debug "Ejected $Drive"
    }
    catch
    {
         Write-Debug "Failed to eject $Drive"
         Write-Debug $_.Exception.Message
    }
}
'@    
    #The password key is only usable by the domain user who created it. 
    #Use the Create-SecretPasswordFile function to create one for your login
    $LocalCDEjectorScriptString = $LocalCDEjectorScriptString
    $LocalCDEjectorScript = [scriptblock]::Create($LocalCDEjectorScriptString)
    
    Write-Debug $LocalCDEjectorScriptString
    
    $job = Invoke-Command -Session $Session -Scriptblock $LocalCDEjectorScript  -AsJob
    $Null = Wait-Job -Job $Job

}


$Session = Create-PSRemotingSession
Eject-CD -Session $Session
Remove-PSSession -Session $Session

Download

The scripts can be downloaded here: CDEjector.zip

Ausblick

Too late for Halloween 2011, but quite early for Halloween2012.

Tags:

PowerShell | Virtualization | Tips & Tricks | How To | IIS

About Klaus Graefensteiner

I like the programming of machines.

Add to Google Reader or Homepage

LinkedIn FacebookTwitter View Klaus Graefensteiner's profile on Technorati
Klaus Graefensteiner

Klaus Graefensteiner
works as Developer In Test and is founder of the PowerShell Unit Testing Framework PSUnit. More...

Open Source Projects

PSUnit is a Unit Testing framwork for PowerShell. It is designed for simplicity and hosted by Codeplex.
BlogShell is The tool for lazy developers who like to automate the composition of blog content during the writing of a blog post. It is hosted by CodePlex.

Administration

About

Powered by:
BlogEngine.Net
Version: 1.6.1.0

License:
Creative Commons License

Copyright:
© Copyright 2012, Klaus Graefensteiner.

Disclaimer:
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Theme design:
This blog theme was designed and is copyrighted 2012 by Klaus Graefensteiner

Rendertime:
Page rendered at 2/5/2012 9:41:41 PM (PST Pacific Standard Time UTC DST -7)