Pulse virtual machines programmatically

8. April 2009

I use Hyper-V frequently for testing purposes. Virtualization has a lot of advantages. There is one thing (ok, there is more but let’s concentrate on a particular one today) that bothers me. All my machines joined our company Active Directory. The problem is that if a machine doesn’t run for a certain time (I guess something like 2 weeks) the machine account will be removed from Active Directory. It’s not a huge deal, you just have to:

  • login as administrator
  • remove your computer from the domain
  • restart
  • join the domain again
  • restart

Even thought it’s not such a big deal it annoys me every time I need to do that. I thought that if I would be able to automatically start each machine (let’s say at night when I’m not using the computer) it would solve this problem. Since I use PowerShell I wanted to be able to leverage PowerShell for this.

Retrieve all available VMs

To retrieve all available VMs we need to execute a WMI query. The trick with this query is that it will retrieve all machines including your host machine. That’s why you need to add additional filter where you ask to retrieve only machines which caption starts with “Virtual”.

 

function get-vms
{
    $query = "SELECT * FROM MsVM_ComputerSystem WHERE Caption Like 'Virtual%' "
    get-wmiobject -query $query -namespace "root\virtualization" -computername "." 
}

 

Retrieve a particular VM

Once we have a collection of all available VMs, we will execute some actions on them. As a helper I will also create a function that will retrieve VM object when provided a machine name:

 

function get-vm([string] $name)
{
    #Get the VM Object
    $query = "SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" + $name + "'"
    get-wmiobject -query $query -namespace "root\virtualization" -computername "."
}

 

Starting VM

To start a VM, we just need to set RequestStateChange to value 2. The code to do so is very simple.

 

function start-vm([string] $name)
{
    $VM = get-vm $name
    
    #Request a state change on the VM
    $result = $VM.RequestStateChange(2)
}

 

Shutting down VM

To shutdown a VM, we need to get a special ShutdownComponent and call InitiateShutdown method. The code might seem complex but it isn’t. Most of the code bellow is just error handling code.

 

function shutdown-vm([string] $name)
{
    #Get the Shutdown Component for the VM
    $vm = get-vm $name
    $query = "SELECT * FROM Msvm_ShutdownComponent WHERE SystemName='" + $vm.name + "'"
    $Shutdown = get-wmiobject -query $query -namespace "root\virtualization" -computername "." 
    
    if ($Shutdown -ne $null)
    {
        #Request a forced shutdown
        $result = $Shutdown.InitiateShutdown($true,"Shutdown initiated from PowerShell script")
        if ($result.returnValue -eq 0) 
        {
            write-host "Shutdown of '$name' started."
        } 
        else 
        {
            write-host "Attempt to shutdown 'name' failed with code $($result.returnValue)."
        } 
    }
    else  
    {
        write-host "Could not get shutdown component for '$name'."
    }
}

 

Final script

To put it all together we need to retrieve all VMs and call start and shutdown in a loop for each of the retrieved VMs.

 

$VMs = get-vms
foreach ($VM in $VMs)
{
    $name = $VM.ElementName
    write-host $name
    
    write-host "`tStarting...."
    start-vm $name
    
    write-host "`tWaiting to fully load for 7 minutes..."
    start-sleep -seconds 420
    
    write-host "`tShutdown..."
    shutdown-vm $name
}

write-host "Finished."

 

Let me note that in the previous script I added a sleep for 7 minutes. Why do I do that? I want to give the VM enough time to completely start all the services.

 

Remarks

Let me add that I found that shutdown can also be done using “associators”. In the above code we use the following query:

    $vm = get-vm $name
    $query = "SELECT * FROM Msvm_ShutdownComponent WHERE SystemName='" + $vm.name + "'"
    $Shutdown = get-wmiobject -query $query -namespace "root\virtualization" -computername "." 

The same can be done using “associators”.

 

    $vm = get-vm $name
    $query = "Associators of {$vm} Where AssocClass=Msvm_SystemDevice ResultClass=Msvm_ShutdownComponent"
    $Shutdown = get-wmiobject -query $query -namespace "root\virtualization" -computername "." 

I don’t know which one is better but both of them seems to work just fine on my machine. If you do know the difference then please let me know.

 

More information

More information can be found at:

 

Attachments:

PowerShell

Configuring TeamCity with SQL Server

1. April 2009

This is the second post in my two post mini series where I try to describe my development environment. I have already mentioned that I use TeamCity as my continuous integration and build system. In this post I will describe how to configure TeamCity to use SQL Server as a main configuration repository.

Installation of TeamCity

Run TeamCity installation to install the build server and build agent on your server. This will give you fully functional build system that is integrated with your development components. It will automatically (when configured appropriately) trigger build when a change is submitted into your VisualSVN Server repository. The build will use MSBuild to build all the artifacts of your project including running unit tests, generating source code documentation, etc. When finished the system will automatically label the source code after a successful build.

Currently all configuration is written into HSQLDB database.

 

Microsoft SQL Server as a configuration repository

Assumtions: You have 64-bit operating system and you have installed TeamCity into C:\Program Files (x86)\JetBrains\TeamCity and your build server configuration is at C:\BuildServer folder.

 

JDBC driver

TeamCity is written in Java and therefore we need Java drivers for Microsoft SQL Server. Download and install JDBC driver from Microsoft SQL Server JDBC Driver page. Copy sqljdbc.jar and auth/x64/sqljdbc_auth.dll into C:\Program Files (x86)\JetBrains\TeamCity\webapps\ROOT\WEB-INF\lib.

 

Configure SQL Server for JDBC driver

Open SQL Server Configuration Manager and make sure that SQL Server (MSSQLSERVER) is up and running and that TCP/IP protocol is enabled (using port 1433).

 

TeamCity_SqlProtocol

Create SQL Server database

Open SQL Server Management Studio and add a new Login named teamcity. In this example I will use SQL Server authentication with password teamcity (because I chose such a simple password I need to uncheck Enforce password policy).

Create a new database named TeamCity and add teamcity user to the newly created database as db_owner.

 

Configure TeamCity migration tool

Edit C:\Program Files (x86)\JetBrains\TeamCity\bin\dbMigration.properties file and make sure that sourceURL is set correctly:

sourceURL=jdbc:hsqldb:file:C:/BuildServer/system/buildserver

Now we need to add target configuration for the migration. This is where we add information about our newly created SQL Server database:

# MSSQL 2008 via JDBC 
targetDriver=com.microsoft.sqlserver.jdbc.SQLServerDriver targetURL=jdbc:sqlserver://dpokluda:1433;database=TeamCity; 
targetUser=teamcity 
targetPassword=teamcity

 

Configuration migration

To execute the migration follow these steps. Start PowerShell (or any other shell but you will need to change the bellow mentioned steps).

Add Java to your Path variable so that migration tool can run it. This step is only necessary when you don’t have Java installed on your system and you want to use the one that comes with TeamCity.

$env:Path += ";C:\Program Files (x86)\JetBrains\TeamCity\jre\bin"

Stop all TeamCity services.

stop-service TCBuildAgent 
stop-service TeamCity

Navigate to TeamCity bin folder and start the migration.

cd "C:\Program Files\JetBrains\TeamCity\bin"
migrateDB.bat migrate

(It is fine to press just [Enter] key here since we have entered the full path into the dbMigration.properties file - otherwise enter C:/BuildServer path.)

Start TeamCity services.

start-service TCBuildAgent 
start-service TeamCity

 

Verify functionality of the new repository

Navigate to your TeamCity web page and add a new user in TeamCity administration named Test.

Open TeamCity database in SQL Server Management Studio and you should see that there is a new user named Test in users table:

SELECT TOP 10 * FROM [TeamCity].[dbo].[users]

 

TeamCity_VerifyUsers

For more information see the following resources:

Coding , , ,

Automating installations with PowerShell script

18. March 2009

I am currently testing Windows 7 and therefore I am deploying various builds quite frequently. This is nice because you have a chance to see the product evolving and at the same time you have a chance to send feedback to the product team. On the other hand installing OS frequently means to install all the other applications very often. I have created various PowerShell scripts to simplify the deployment process. Today I will try to describe my install-folder script.

The basic idea of the script is this. I want to put all my installations into a folder and then run the script against this folder. The script will enumerate over all the files in that folder and “run the installation” for each of them. The problem here is that some installations are delivered as MSI files, some are EXE files (not build using Windows Installer) and some are ISO files (those downloaded from MSDN usually).

MSI File Installation

To install an MSI file you run msiexec with /i msiFileName. If you want to run the installation automatically then add also /passive. If you want to prevent Windows Installer from automatically restarting your machine then use /norestart.

Note: When you need to troubleshoot MSI installation, it might be a good idea to turn on verbose logging in Windows Installer. To turn the logging on use /l*v logFilePath parameter. More information about this topic can be found at:

EXE File Installation

This one is tricky. If it is EXE created using Windows Installer then you might be able to use some of the Windows Installer parameters. On the other hand many other installers are available (like InnoSetup, Nullsoft Install System, etc.) and the parameters for these are different. That’s why when installing EXE I usually just invoke the EXE file.

ISO File Installation

ISO file installation is the most difficult because in the OS there is no direct support for these files. There are various utilities available for this purpose (SlySoft Virtual CloneDrive, Daemon Tools, Microsoft Virtual CD-ROM, etc.). I have decided to use SlySoft solution because I have had a really good experience with their other tools like AnyDVD (this is an awesome tool if you are dealing with DVDs from different regions for example). After you install the tool you can mount an ISO image by calling VCDMount.exe isoFilePath. To unmount the current ISO image execute VCDMount.exe /u.

InstallFile function

So to install the files from a folder we need to enumerate over all files in that folder and for each of these files execute InstallFile function:

    function InstallFile($file)
    {
        if ($file.EndsWith(".msi"))
        {
            execute-command -wait -command msiexec -parameters "/i $file /passive /norestart"
        }
        elseif ($file.EndsWith(".exe"))
        {
            execute-command -wait -command "$file"
        }
        elseif ($file.EndsWith(".iso"))
        {
            execute-command -command "$programFiles32\Elaborate Bytes\VirtualCloneDrive\VCDMount.exe" -parameters "/u"
            execute-command -command "$programFiles32\Elaborate Bytes\VirtualCloneDrive\VCDMount.exe" -parameters "$file"
            Pause "`tPress any key when finished with this installation."
            execute-command -command "$programFiles32\Elaborate Bytes\VirtualCloneDrive\VCDMount.exe" -parameters "/u"
        }
    }

 

This function references some of my other scripts/functions. First of all it uses execute-command script. The core of that script is the following:

param([string] $command = $(throw "Missing: command parameter"), [string[]] $parameters, [switch] $wait, [switch] $cmd)

#Main
    if ($cmd.IsPresent)
    {
        cmd /C $command $parameters
    }
    else
    {
        $process = [Diagnostics.Process]::Start($command, $parameters); 
        if ($wait.IsPresent)
        {
            $process.WaitForExit();
        }
    }

Save this script as execute-command.ps1.

The only extra thing we have in our InstallFile folder is reference to $programFiles32 folder. This is initialized in my profile script (which I will describe in one of the latter posts). For now I will just define the variables:

    #Define ProgramFiles* variables
    $is64 = $(if([IntPtr]::Size -eq 8) { $true } else { $false })
    if ($is64)
    {
        $programFiles32 =  $(get-item "env:ProgramFiles(x86)").Value
        $programFiles64 = $env:ProgramFiles
    }
    else
    {
        $programFiles32 = $env:ProgramFiles
        $programFiles64 = $env:ProgramFiles
    }

InstallFolder function

Now we have all we need to implement our main InstallFolder function:

    function InstallFolder($folder)
    {
        $files = get-childitem $folder -recurse -force
        foreach($file in $files)
        {
            if ($file -eq $null)
            {
                continue
            }
            $answer = Ask "Do you want to install $($file.FullName)? (y or n)?" "y"
            if ($answer -ne "y") { continue } 
            set-color "yellow"
            write-host "`tInstalling $file..."
            set-color
            InstallFile $($file.FullName)
        }
    }

In this function I am using two additional functions. Function set-color (is defined in profile script) and Ask function.

    function Ask($text, $default)
    {
        $answer = $(read-host "$text [default: $default]")
        if ($answer -eq "") { $answer = $default }
        return $answer
    }

    function Set-Color([string] $color)
    {
        if ($color -eq "")
        {
            # $myDefaultColor initialized in profile script to [Console]::ForeGroundColor
            $color = $myDefaultColor
        }
        $host.UI.RawUI.ForeGroundColor = $color
    }

Final Install-Folder script code

With all these we can now create the install-folder.ps1 script:

param([string] $path = $(throw "Missing: path parameter"), )

#Internals
    function Ask($text, $default)
    {
        $answer = $(read-host "$text [default: $default]")
        if ($answer -eq "") { $answer = $default }
        return $answer
    }

    function Set-Color([string] $color)
    {
        if ($color -eq "")
        {
            # $myDefaultColor initialized in profile script to [Console]::ForeGroundColor
            $color = $myDefaultColor
        }
        $host.UI.RawUI.ForeGroundColor = $color
    }

    function InstallFolder($folder)
    {
        $files = get-childitem $folder -recurse -force
        foreach($file in $files)
        {
            if ($file -eq $null)
            {
                continue
            }
            $answer = Ask "Do you want to install $($file.FullName)? (y or n)?" "y"
            if ($answer -ne "y") { continue } 
            set-color "yellow"
            write-host "`tInstalling $file..."
            set-color
            InstallFile $($file.FullName)
        }
    }
    
    function InstallFile($file)
    {
        if ($file.EndsWith(".msi"))
        {
            execute-command -wait -command msiexec -parameters "/i $file /passive /norestart"
        }
        elseif ($file.EndsWith(".exe"))
        {
            execute-command -wait -command "$file"
        }
        elseif ($file.EndsWith(".iso"))
        {
            execute-command -command "$programFiles32\Elaborate Bytes\VirtualCloneDrive\VCDMount.exe" -parameters "/u"
            execute-command -command "$programFiles32\Elaborate Bytes\VirtualCloneDrive\VCDMount.exe" -parameters "$file"
            Pause "`tPress any key when finished with this installation."
            execute-command -command "$programFiles32\Elaborate Bytes\VirtualCloneDrive\VCDMount.exe" -parameters "/u"
        }
    }

#Main
    #Define ProgramFiles* variables
    $is64 = $(if([IntPtr]::Size -eq 8) { $true } else { $false })
    if ($is64)
    {
        $programFiles32 =  $(get-item "env:ProgramFiles(x86)").Value
        $programFiles64 = $env:ProgramFiles
    }
    else
    {
        $programFiles32 = $env:ProgramFiles
        $programFiles64 = $env:ProgramFiles
    }

    write-host "Installing $path..."
    InstallFolder $path

This is just one of my scripts that I use daily. I will describe more of them in later posts.

Attachments:

PowerShell