Basic smoke test automation with PowerShell and VMWare Workstation

by Klaus Graefensteiner 8. April 2011 09:34

Introduction

What is the worst programming language you have ever worked with? Mine is Nant, especially if you try to use it for creating very sophisticated software staging and testing workflow scripts. Using Nant for Windows automation instead of PowerShell is insane. For all these people out there that don’t know enough about PowerShell and need to be weaned off Nant, here is an example that might convince you switching.

solder

Figure 1: Picking the right technology for maximum productivity. Circuit board –> Nant –> PowerShell

This blog post features a PowerShell script that installs software on a clean virtual machine and sends an email when it is complete. It uses PowerShell Remoting to extract a zip file on the remote machine and to install the extracted MSI file.

Sequence

Here is the sequence of steps that the script is orchestrating.

  • Revert VMWare Image
  • Start VMWare Image
  • Copy zip file into a share on the VMWare image
  • Unzip file using PowerShell Remoting
  • Launch MSI install via PowerShell Remoting
  • Run tests (Tests are not included in this example, but can be easily scripted of course)
  • Send email notification once some test is finished

Scripts

Batch file to feed parameters to batch file that calls PowerShell script

run.bat 9.200.34456.0 20110409 \\bs\bs\9\9.200.34456.0

Batch file that calls PowerShell script

@echo %1
@echo %2
@echo %3



PowerShell -command "E:\SmokeTesting\Smoke-Test.ps1 %1 %3" -NoExit

PAUSE

Smoke test PowerShell script

Param([string] $BuildNumber="9.200.34456.0", [string] $BuildFolder="\\bs\bs\9\9.200.34456.0")

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

Write-Debug $BuildFolder
Write-Debug $BuildNumber
$ScriptFileFolder = Split-Path -Path $($MyInvocation.MyCommand.Definition) -Parent
Write-Debug $ScriptFileFolder


function Load-Configuration([string] $ConfigurationFileName)
{   
    $ConfigFilePath = Join-Path -Path $ScriptFileFolder -ChildPath $ConfigurationFileName
    Write-Debug $ConfigFilePath
    if(Test-Path -Path $ConfigFilePath)
    {
      . $ConfigFilePath        
    }
    else
    {
        Throw "Configuration file `"$ConfigFilePath`" not found!"
    }
}

function Revert-SnapShot([string] $VMPath, [string] $SnapShotName)
{
    $QuotedVMPath = "`"{0}`"" -f $VMPath
    $Result = Start-Process -FilePath $Config.VMRunUtiltyPath -ArgumentList "-T", "ws", "revertToSnapShot", $QuotedVMPath, $SnapShotName -Wait -PassThru -NoNewWindow
    Write-Debug ($Result.HasExited)
    Write-Debug ($Result.ExitCode)
}

function Start-VM([string] $VMPath)
{
    $QuotedVMPath = "`"{0}`"" -f $VMPath
    $Result = Start-Process -FilePath $Config.VMRunUtiltyPath -ArgumentList "-T", "ws", "start", $QuotedVMPath -Wait -PassThru -NoNewWindow
    Write-Debug ($Result.HasExited)
    Write-Debug ($Result.ExitCode)
    [System.Threading.Thread]::Sleep($Config.VMSecondsToWaitAfterImageStart * 1000)
    
}

function Copy-BuildZipFile([string] $BuildFolder, [string] $Target)
{
    $PackagesPath = Join-Path -Path $BuildFolder -ChildPath $Config.BuildImageRelativePath
    $ZipFile = Get-ChildItem -Path $PackagesPath -Filter $Config.BuildZipFileNameFilter
    Copy-Item -Path $ZipFile.FullName -Destination $Target -Force
}

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 Send-SuccessMail([string] $BuildNumber)
{
    Send-MailMessage -Body $Config.EmailBody -From $Config.EmailFrom -SmtpServer $Config.EmailSMTPServer -Subject ($Config.EmailSubject -f $BuildNumber) -To $Config.EmailTo
}

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

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 Unzip-Remote($Session)
{

    $UnzipScriptString = @'
        $ShellApplication = new-object -com "shell.application"
        $PathWithFilter = "{0}*.zip"
        $ZipFileName = Get-Item -Path $PathWithFilter
        $ZipPackage = $ShellApplication.NameSpace($ZipFileName.FullName)
    	$DestinationFolder = $ShellApplication.NameSpace("{1}")
    	$DestinationFolder.CopyHere($ZipPackage.Items())
'@
    
    $UnzipScriptString = $UnzipScriptString  -f $Config.StagingPathOnVM, $Config.ExtractZipToPathOnVM
    $UnzipScript = [scriptblock]::Create($UnzipScriptString)
    
    Write-Debug $UnzipScriptString
    
    $Job = Invoke-Command -Session $Session -Scriptblock $UnzipScript -AsJob
    $Null = Wait-Job -Job $Job
}


function Install-MSIRemote($Session)
{
    $InstallScriptString = @'
    Start-Process -FilePath "msiexec" -ArgumentList "/qn", "/i", "{0}", "SQLSERVERNAME={1}", "SQLAUTHMODE={2}", "SQLUSERNAME={3}", "SQLPASSWORD={4}" -NoNewWindow -PassThru -RedirectStandardError "C:\Landing\stderrorlog.txt" -RedirectStandardOutput "C:\Landing\stdoutlog.txt" -Wait
'@    
    #The password key is only usable by the domain user who created it. 
    #Use the Create-SecretPasswordFile function to create one for your login
    $InstallScriptString = $InstallScriptString -f $Config.ExtractZipMSIPath, $Config.MSISQLServerName, $Config.MSISQLSQLAuthMode, $Config.MSISQLUserName, (Get-DecryptedString (ConvertTo-SecureString $Config.MSISQLPassword))
    $InstallScript = [scriptblock]::Create($InstallScriptString)
    
    Write-Debug $InstallScriptString
    
    $job = Invoke-Command -Session $Session -Scriptblock $InstallScript  -AsJob
    $Null = Wait-Job -Job $Job
}




Write-Debug $(Get-Date)
$Config = Load-Configuration -ConfigurationFileName "bobster_cloud59.ps1"
Revert-SnapShot -VMPath $Config.VMImagePathOnHost -SnapShotName $Config.VMSnapShotName
Start-VM -VMPath $Config.VMImagePathOnHost
Copy-BuildZipFile -PublishedBuildDir $BuildFolder -Target $Config.StagingShareOnVM
$Session = Create-PSRemotingSession
Unzip-Remote -Session $Session
Install-MSIRemote -Session $Session
Remove-PSSession -Session $Session
Send-SuccessMail -BuildNumber $BuildNumber
Write-Debug $(Get-Date)

Configuration file

Write-Debug "Loading Config File"
$Config = @{}
$Config.VMImagePathOnHost="I:\Images\CT2008R2\CT2008R2.vmx"
$Config.VMSnapShotName="SmokeTestReady"
$Config.VMComputerName="cloud59"
$Config.VMRunUtiltyPath="C:\Program Files (x86)\VMware\VMware Workstation\vmrun"
$Config.VMSecondsToWaitAfterImageStart=60
$Config.StagingShareOnVM="\\" + $Config.VMComputerName + "\Landing"
$Config.StagingPathOnVM="C:\Landing\"
$Config.MSISQLServerName="."
$Config.MSISQLSQLAuthMode="SQLServerAuth"
$Config.MSISQLUserName="sa"
#The password key is only usable by the domain user who created it. 
#Use the Create-SecretPasswordFile function to create one for your login
$Config.MSISQLPassword="...00002000000000003660000c00000001000000044fd0a..."
$Config.ExtractZipToPathOnVM= Join-Path -Path $Config.StagingPathOnVM -ChildPath "Temp"
$Config.ExtractZipMSIPath = Join-Path -Path $Config.ExtractZipToPathOnVM -ChildPath "Client\Setup.msi"
$Config.PSRemotingCredentialDomain="sky"
$Config.PSRemotingCredentialUserName="bobster"
#The password key is only usable by the domain user who created it. 
#Use the Create-SecretPasswordFile function to create one for your login
$Config.PSRemotingCredentialPassword="...00002000000000003660000c00330001000000044fd0a..."
$Config.BuildImageRelativePath="web\download"
$Config.BuildZipFileNameFilter="SkyCli*.zip"
$Config.EmailBody="The latest build has been installed on {0}" -f $Config.VMComputerName
$Config.EmailSubject="Build {0} is ready for test {1}" -f "{0}", $Config.VMComputerName
$Config.EmailSMTPServer="smtp.skysmtp.ptm"
$Config.EmailFrom="king_of_the_lab@sky.me"
$Config.EmailTo="devster@sky.me"

return $Config

Smoke test setup instructions

In case you are interested in setting up a smoke test system yourself using VMWare Workstation and PowerShell use the following setup steps:

Install PowerShell

This only applies to older versions of Windows. If you are using Windows 7 or Windows 2008 Server R2 skip this step.

Prepare PowerShell

  • Set-ExecutionPolicy "remotesigned"
  • Enable-PSRemoting

On VM

  • Create folder C:\Landing\Package
  • Create share \\testvm\Landing and give full control to sky\bobster

On Host

  • Create a folder e.g. E:\SmokeTesting and copy smoke test files
  • Rename configuration file and set configuration parameters
  • Change Smoke-Test script file to pick up new configuration file
  • Make sure to use the latest version of VMWare Workstation 7.1.2 or greater
  • Change passwords encodings in configuration file using the Create-SecretPasswordFile function

Download

The script files can be downloaded here: Smoke-Test.zip

Ausblick

This script is be basic framework for running the actual smoke tests checks. To put some more spice to the mix, I am thinking about hooking up PSUnit to run the smoke test verifications and use a VM in the cloud (SkyTap cloud) instead. Stay tuned!

Tags: , , , , , , ,

PowerShell | Virtualization | Automation | MSI | Test Automation | Testing

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 2013, 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 2013 by Klaus Graefensteiner

Rendertime:
Page rendered at 5/18/2013 7:24:42 PM (PST Pacific Standard Time UTC DST -7)