X
    Categories: CloudLinuxNewsWindows

How To Create Windows 2008 Server Contextualized Image in OpenNebula

How To Create Windows 2008 Server Contextualized Image in Opennebula

Preparing Windows Images

The first step is to create a new image file to install windows. If you have QEMU installed, it is easy enough as running.

qemu-img create -f raw newimage.raw 30G

20GB should be enough for Windows 7 64-bit and 30GB suitable for Windows 2008 R2 64-bit, normal installation leaves about 10GB free for other programs you want to install.

To use the image, you can be assisted by Virt-Manager. Create a new machine, select the Windows ISO to install and the path of the just created image and run the machine.

*IMPORTANT – due to Windows compatibility problems, choose Virtio disk and e1000 network card – other hardware can be problematic and cause bluescreens*.

After Windows install convert it to qcow2:

qemu-img convert -O qcow2 newimage.raw newimage.qcow2

After a clean install of a Windows OS on an image file, with all the hardware which will be replicated, it is necessary to do a few steps. If you do not want to do a new installation (i.e. you have lots of programs installed and configurations made), you must do a sysprep to wipe out all the personal and sensitive data.

Syspreping the system

There are many useful guides to sysprep a Windows machine, but we used this guide, http://theitbros.com/sysprep-a-windows-7-machine-%E2%80%93-start-to-finish, to execute it. But we’ll try to shorten it a little bit.

You need: – install Windows® Automated Installation Kit (AIK) for Windows® 7 – ISO or DVD of Windows Installation

After that, run AIK, open the ISO and the file “\sources\install.wim” and choose all the features you want to customize, as locale, timezone, admin account and license key, among many others. Save and your XML is ready to go.

Copy or move your "unattend.xml" file to "C:\windows\system32\sysprep". Run a Command Prompt window with administrative priviledges and execute

“sysprep /generalize /oobe /shutdown /unattend:unattend.xml”. Your system will now be cleaned up, prepared and shutdown after all done.

A sample file is available on wiki files, for Windows 2008 R2 64-bit.

Preparing the system

Before the machine is ready for template, please install whatever software you need for it and that can avoid a few dozens of hours in end-user installations. To ready the machine, you just need to go to Local Group Policy. Just go to Adminstrative Tools->Local Group Policy or simply hit Run->gpedit.msc. Go to the Local Computer Policy->Computer Configuration->Windows Settings->Scripts->Startup and right-click on Properties. Add a new script and write “D:\SetupComplete.cmd”, the file doesn’t exist yet but it won’t complain. Hit OK and you’re ready to go. Make sure there aren’t no folders named “context” on C:\ and shutdown the machine.

Registering/activating Windows

Regardless the method you chose to prepare your Windows image, you’ll have to register it and activate it. If you already have the image activated, made this manually or made it through the Sysprep you’re done. If you don’t have a single license key and want to use a volume license, register it a simple slmrg command:

slmgr.vbs /skms yourlicenseserver.com:port

After a successful communication and an ok feedback, you can activate it with:

slmgr.vbs /ato

And you’re done!

Finalizing the image

After all these steps, we need to take a snapshot of the image and register it on OpenNebula (optional but recommended). To use the copy-on-write property, we’ll use the created Windows image as a base image and work on another file. Now, this is a very important step. Although it enforces us to repeat the steps for each created qcow image, this step allows OpenNebula to continue to use any kind of image other than qcow. When following the official qcow manual (https://support.opennebula.pro/entries/348847-using-qcow-images), OpenNebula is somewhat limited when using qcow images.

qemu-img create -f qcow2 -b /opt/.../win2k8-sql08-vs10.qcow2 snapshot.qcow2

Create a image template “win2k8-sql08-vs10.one” for the snapshot:

NAME          = "Win2k8"
SOURCE        = /opt/opennebula/var/golden-images/win2k8/snapshot.qcow2
TYPE          = OS
PUBLIC        = YES
DRIVER        = qcow2
BUS           = virtio
DESCRIPTION   = "Windows 2008 desktop for students."

And register it with

oneimg create win2k8-sql08-vs10.one

Creating an OpenNebula template

After we created our image, we need to build the ONE template for standard instantiation. Our template for Windows 2008 is:

CONTEXT=[
  FILES="/home/oneadmin/.ssh/id_rsa.pub /opt/opennebula_shared/context-scripts/windows/startup.vbs 
/opt/opennebula_shared/context-scripts/windows/one-context.ps1  /opt/opennebula_shared/context-scripts/windows/unattend.xml 
/opt/opennebula_shared/context-scripts/windows/README.txt /opt/opennebula_shared/context-scripts/windows/SetupComplete.cmd",
  HOSTNAME=Win2008-$VMID,
  IP_PUBLIC="$NIC[IP, NETWORK=\"Aulas Network\"]",
  PASSWORD=thepassword,
  ROOT_PUBKEY=id_rsa.pub,
  USERNAME=theusername
]
CPU=1
DISK=[
  BUS=virtio,
  DRIVER=qcow2,
  READONLY=no,
  IMAGE_ID = 12, #(or "SOURCE=/opt/opennebula/var/golden-images/win2k8/snapshot.qcow2," if you didn't register the image)
  TARGET=vda,
  TYPE=disk ]
FEATURES=[ ACPI=yes ]
GRAPHICS=[
  TYPE=vnc ]
MEMORY=2048
NAME=Windows2008-SQL2008-VS2010
NIC=[
  MODEL=e1000,
  NETWORK_ID=4 ]
OS=[
  ARCH=x86_64,
  BOOT=hd ]
RAW=[ TYPE=kvm ]

It can be pasted on a new file “Windows2008.one” or created on the web browser on Sunstone, on section Templates->Advanced. Adapt your settings on the template and its done! You can start a new VM with the new template in 10s!

FILES NEEDED

SetupComplete.cmd

SetupComplete.cmd is a file which is really helpful, because it can quietly be called as a startup script on Windows. Basically, it calls a VBScript on the attached context-created disk D. If the system has been syspreped, it erases the unattend file, to protect the installation credentials.

cscript //b d:/startup.vbs
del /Q /F c:\windows\system32\sysprep\unattend.xml
del /Q /F c:\windows\panther\unattend.xml

startup.vbs

This Visual Basic script, which can be called from a regular command prompt on boot, must run with privileges to modify script executing on the machine. Therefore it is called by the previous file SetupComplete.cmd on Windows startup. About its functions, it calls the Powershell console with unrestriced policy to execute the Powershell script on context-created disk D. Afterwards, it creates a new directory just to allow the admin user to ensure the script execution.

Set objShell = CreateObject("Wscript.Shell")
objShell.Run("powershell -NonInteractive -NoProfile -NoLogo -ExecutionPolicy Unrestricted -file D:\one-context.ps1")
Dim objFSO, objFolder
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.CreateFolder("C:\executedVBScript")

one-context.ps

This was the most difficult script to write, because it had many walls to break. The Powershell script runs to configure the new VM as a whole.

#################################################################
##### Windows Powershell Script to configure OpenNebula VMs #####
#####   Created by andremonteiro@ua.pt and tsbatista@ua.pt  #####
#####        DETI/IEETA Universidade de Aveiro 2011         #####
#################################################################

Set-ExecutionPolicy unrestricted -force # not needed if already done once on the VM
[string]$computerName = "$env:computername"
[string]$ConnectionString = "WinNT://$computerName"

function getContext($file) {
	$context = @{}
	switch -regex -file $file {
		'(.+)="(.+)"' {
			$name,$value = $matches[1..2]
			$context[$name] = $value
		}
	}
	return $context
}

function addLocalUser($context) {
    # Create new user
        $username =  $context["username"]
        $ADSI = [adsi]$ConnectionString

        if(!([ADSI]::Exists("WinNT://$computerName/$username"))) {
           $user = $ADSI.Create("user",$username)
           $user.setPassword($context["password"])
           $user.SetInfo()
        }
        # Already exists, change password
        else{
           $admin = [ADSI]"WinNT://$env:computername/$username"
           $admin.psbase.invoke("SetPassword", $context["PASSWORD"])
        }

    # Set Password to Never Expires
    $admin = [ADSI]"WinNT://$env:computername/$username"
    $admin.UserFlags.value = $admin.UserFlags.value -bor 0x10000
    $admin.CommitChanges()

    # Add user to local Administrators
    $groups = "Administrators", "Administradores"

    foreach ($grp in $groups) {
    if([ADSI]::Exists("WinNT://$computerName/$grp,group")) {
                $group = [ADSI] "WinNT://$computerName/$grp,group"
                        if([ADSI]::Exists("WinNT://$computerName/$username")) {
                                $group.Add("WinNT://$computerName/$username")
                        }
                }
        }
}

function getIp($mac) {
    $mac = $mac.Replace("-",":")
    $octet = $mac.Split(":")
    [String] $ip = ""
    $ip += [convert]::toint32($octet[2],16)
    $ip += "."+[convert]::toint32($octet[3],16)
    $ip += "."+[convert]::toint32($octet[4],16)
    $ip += "."+[convert]::toint32($octet[5],16)
    return $ip
}

function getGateway($mac) {
    $octet = $mac.Split(":")
    [String] $ip = ""
    $ip += [convert]::toint32($octet[2],16)
    $ip += "."+[convert]::toint32($octet[3],16)
    $ip += "."+[convert]::toint32($octet[4],16)
    $ip += ".254"
    return $ip
}

function configureNetwork($context) {
    $Nics = Get-WMIObject Win32_NetworkAdapterConfiguration | where {$_.IPEnabled -eq "TRUE" -and ($_.MACAddress)} 
    foreach ($nic in $Nics) {
        [String]$mac = $nic.MACAddress
        [String]$ip = getIp($mac)
        [String]$gw = getGateway($mac)
        $nic.ReleaseDHCPLease()
        $nic.EnableStatic($ip , "255.255.255.0")
        $nic.SetGateways($gw)
        $DNSServers = "193.136.172.20", "193.136.171.21"
        $nic.SetDNSServerSearchOrder($DNSServers)
        $nic.SetDynamicDNSRegistration("TRUE")
        $nic.SetWINSServer($DNSServers[0], $DNSServers[1])
    }
}

function renameComputer($context) {
    $ComputerInfo = Get-WmiObject -Class Win32_ComputerSystem  
    $ComputerInfo.rename($context["HOSTNAME"])
}

function enableRemoteDesktop()
{
    # Windows 7 only - add firewall exception for RDP
    netsh advfirewall Firewall set rule group="Remote Desktop" new enable=yes
    
    # Enable RDP
    $Terminal = (Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace root\cimv2\terminalservices).SetAllowTsConnections(1)
    return $Terminal
}

function enablePing()
{
    #Create firewall manager object
    $FWM=new-object -com hnetcfg.fwmgr

    # Get current profile
    $pro=$fwm.LocalPolicy.CurrentProfile
    $pro.IcmpSettings.AllowInboundEchoRequest=$true
}

function addReadme($context) {
 	$username =  $context["USERNAME"]
        Copy-Item D:\README.txt C:\Users\$username\Desktop\README.txt"
}

# If folder context doesn't exist create it
if (-not (Test-Path "c:\context\")) {
    New-Item "C:\context\" -type directory
    }
    
# Execute script    
if( -not(Test-Path "c:\context\contextualized") -and (Test-Path "D:\context.sh")) {
    $context = @{} 
    $context = getContext('D:\context.sh')
    addLocalUser($context)
    renameComputer($context)
    enableRemoteDesktop
    enablePing
    addReadme($context)
    Start-Sleep -s 30
    configureNetwork($context) 
    echo "contextualized" |Out-File ("c:\context\contextualized")
    restart-computer -force
}
 ## Restart a second time to ensure network connection
else if( -not(Test-Path "c:\context\contextualizedNetwork") -and (Test-Path "D:\context.sh"))
{
    $context = @{}
    $context = getContext('D:\context.sh')
    configureNetwork($context)

    addReadme($context)
    echo "contextualizedNetwork" |Out-File ("c:\context\contextualizedNetwork")
}

 

context.sh

Context.sh is the context file created by OpenNebula from the image template. It has several attributes, which are filled on every instantiation. The example below is of an instantiated VM. The files attribute indicates that ONE is going to build a context disk in drive D with that list of files. Hostname, IP, username and password can be modified and include variables such as $VMID.

CONTEXT=[
  FILES="/opt/opennebula_shared/context-scripts/windows/startup.vbs /opt/opennebula_shared/context-scripts/windows/one-context.ps1 
/opt/opennebula_shared/context-scripts/windows/unattend.xml /opt/opennebula_shared/context-scripts/windows/README.txt
/opt/opennebula_shared/context-scripts/windows/SetupComplete.cmd",
  HOSTNAME=Win2008-681,
  IP_PUBLIC=192.168.1.1
  PASSWORD=password,
  USERNAME=username]

README.txt

This text file is only for user integration, it can have a couple of rules when using the machine

Configuration rules
----------------------
- once logged in, change initial passwords
- keep always copy of important data and execute regular backups (i.e. DB)
- when the machine is not needed anymore, shut it down and ask the administrator to remove it

Windows
----------------------
User:theuser
Password:thepassword

SQL Server
----------------------
User:theuser
Password:thepassword
Pedro :