SHIFT

--- Sjoerd Hooft's InFormation Technology ---

User Tools

Site Tools


Sidebar

Recently Changed Pages:

View All Pages


View All Tags


LinkedIn




WIKI Disclaimer: As with most other things on the Internet, the content on this wiki is not supported. It was contributed by me and is published “as is”. It has worked for me, and might work for you.
Also note that any view or statement expressed anywhere on this site are strictly mine and not the opinions or views of my employer.


Pages with comments

View All Comments

tfsscvmmnewvm

Create a New VM Using SCVMM Through TFS

After we started with Getting Started With TFS Server 2018 and created a complete CI/CD Build and Release pipeline we will now use TFS to create a new VM using SCVMM. The goal is to create a Build Definition to easily deploy new VMs on demand, providing essential information as the name and IP addresses during schedule time. In this way it is possible for other TFS users to re-use the build to deploy their own VMs.

SCVMM Integration

Besides using script to deploy VMs you could also natively integrate TFS and SCVMM using the System Center Virtual Machine Manager (SCVMM) extension from the Marketplace. That provides a few benefits, such as you don't have to maintain your own scripts, but it also has a downside being that not everything you would be able to do with your own script is also supported. I think however that using the extension provides enough benefit in this case as it is the purpose that other users can re-use the build definition.

Build Agent

In my case, the build agent we installed here already has the SCVMM console installed. If not, you should do so as it is a requirement for the extension to work with a build agent.

System Center Virtual Machine Manager (SCVMM) Extension

Then we need to install the extension. This can be done in the same way as we previously installed the Replace Tokens extension.

SCVMM Service Connection

Then we need to create a service connection so the SCVMM extension has the correct credentials and information to connect to Hyper-V:

  • Go to the project and click the gear icon → Services
  • Click +New Service Endpoint and select SCVMM from the list. In the new window, provide the following information:
    • Connection name: Enter a descriptive name like “mgmt vmmserver”
    • SCVMM Server Name: vmm.management.getshifting.com:8100
    • Username: MGMT\service_account_infra
    • Password: *
    • Click OK to save the scvmm connection

Create Build

Before we can do anything else, we now have to create a build definition. In the TFS console go to Build and Release → Builds and click on +New. Select to start with an empty process, give a name and make sure to select the correct Agent queue, so the one with the build agent in it that has the SCVMM console installed. Click “Save & queue”, and just save the build definition for now.

Build Variables

Now we first add all the required variables to the build so we can immediately configure the build steps properly when we'll add them. From the build definition we just created, click the Variables tab. You are presented with the default process variables. Now if you will be using the variables in more than 1 build definition, you might want to consider creating a variable group. For now, we'll add the variables as a process variable.
To add a variable, click the +Add link at the bottom of the current variable list. A new, underlined line appears with a couple of options:

  • Name: the name of the variable. This is the name you'll use in the steps and scripts to identify the variable.
  • Value: the default value of the variable. If you don't set the “Settable at queue time” option (see below), this is the value used for the variable.
  • Secret (lock icon): If you hover over the new line, this icon appears. If you would click that the variable will be used as a secret, and the value will never be visible to users or in the log after you've set it. You can use this for passwords.
  • Settable at queue time: If you hover over the new line, a checkbox appears in the Settable at queue time column. If you would enable this option you'd be given the option to change the value every time you queue a build. This setting is especially useful in our case as we want users to be able to change for example the vmname and the IP address of the VM.

For everything to work we need the following variables:

  • mgmtdomainautomationaccount: The privileged account for the management domain: mgmt\service_account_infra
  • mgmtdomainautomationaccountpass: The password of the privileged account. Set as secret.
  • vmnetwork: The name of the VM network, for example “Network - Development”. Set as settable at queue time.
    • A list of all VM networks can be seen in the SCVMM console → VMS and Services → VM networks
    • Production: “Network - Production”
    • Acceptance: “Network - Acceptance”
  • vlanid: The VLAN ID for the network: 3003. Set as settable at queue time.
    • Production: 3001
    • Acceptance: 3002
  • vmname: The name of the VM. This will also be used as the computername. Set as settable at queue time.
  • hostname: The name of the hyper-v host: developmentcluster-node1.management.getshifting.com. Set as settable at queue time.
    • Production: productioncluster-node1.management.getshifting.com
  • ipaddress: 192.168.100.100. Set as settable at queue time.
  • subnet: 255.255.255.0. Set as settable at queue time.
  • defaultgateway: 192.168.100.1. Set as settable at queue time.
  • dnsserver: 192.168.100.53. Set as settable at queue time.
    • You can only set one dns server during deployment. Additional DNS servers can be set after deployment, preferrably with tools like GPO, DHCP, etc.
Note that variables with spaces need to be set between quotes: “value”. This will not work for every task, so try to keep spaces out of objects and names thta have a part in TFS automation tasks.


Note that you can also add the variables later if you're not sure which variables you need.

Build Tasks

Now we can add the required tasks to the build definition. Inside the build definition, click on the tasks tab and click “+ Add Task” to add a task. Follow the specifics of the steps below to configure the task correctly.

SCVMM - New VM

We'll deploy a VM using a template. First we need to add a SCVMM task. Click “+ Add Task” and search for SCVMM. Click the SCVMM task and click Add. Configure the task like this:

  • Display Name: SCVMM :NewVM action (default, after selecting the correct action)
  • SCVMM Service Connection: Select the “mgmt vmmserver” service connection
  • Action: New Virtual Machine using Template/stored VM/VHD
  • Create virtual machine from VM templates: Selected
  • Specify VM template using: VM template names
  • Virtual machine names: $(vmname)
  • VM template names: Q1-2019
  • Deploy the VMs to: Host
  • Host name: $(hostname)
  • Additional Arguments (default): -StartVM -StartAction NeverAutoTurnOnVM -StopAction SaveVM
  • Wait Time: 180
    • Lowering this speeds up the process, not sure yet how low you can go. Default is 600.

SCVMM - Stop VM

First we need to add a SCVMM task. Click “+ Add Task” and search for SCVMM. Click the SCVMM task and click Add. Configure the task like this:

  • Display Name: SCVMM :StopVM action (default, after selecting the correct action)
  • SCVMM Service Connection: Select the “mgmt vmmserver” service connection
  • Action: Stop Virtual Machine
  • VM Names: $(vmname)

SCVMM - PowerShell Action - Set NetworkAdapter

First we need to add the proper PowerShell Script to the code repository. Go to Code → PowerShell folder (create it if it doesn't exist yet). Create a new file as scvmmaddnetwork.ps1 and add the following code:

Param(
   [string]$scvm,
   [string]$vmnetworkname,
   [string]$vlanid
)
 
Write-Host "Input = $scvm - $vmnetworkname - $vlanid"
 
$VirtualNetworkAdapter = Get-SCVirtualNetworkAdapter -VM $scvm
Write-Host "1 = $VirtualNetworkAdapter"
$VMNetwork = Get-SCVMNetwork $VMNetworkname
Write-Host "2 = $VMNetwork"
$PortClassification = Get-SCPortClassification -VMMServer vmm.management.getshifting.com | where {$_.Name -eq "Medium bandwidth"}
Write-Host "3 = $PortClassification"
 
Set-SCVirtualNetworkAdapter -VirtualNetworkAdapter $VirtualNetworkAdapter -VMNetwork $VMNetwork -MACAddressType Dynamic -VLanEnabled $true -VLanID $VLANID -VirtualNetwork "Switch" -IPv4AddressType Dynamic -IPv6AddressType Dynamic -PortClassification $PortClassification | Out-Null
Note that to see script output in the TFS logs you need to explicitly use write-host statements.
Note that in case you have a CI configured you can add the {***NO_CI*** comment to prevent the CI process to trigger.

Secondly we need to add a SCVMM task. Click “+ Add Task” and search for SCVMM. Click the SCVMM task and click Add. Configure the task like this:

  • Display Name: SCVMM :ExecutePS action - Set networkadapter
  • SCVMM Service Connection: Select the “mgmt vmmserver” service connection
  • Action: Run Powershell Script For SCVMM
  • Script Type: Script File Path
  • Script Path: powershell/scvmmaddnetwork.ps1
  • Script Arguments: -scvm $(vmname) -vmnetworkname $(vmnetwork) -vlanid $(vlanid)

SCVMM - Start VM

First we need to add a SCVMM task. Click “+ Add Task” and search for SCVMM. Click the SCVMM task and click Add. Configure the task like this:

  • Display Name: SCVMM :StartVM action (default, after selecting the correct action)
  • SCVMM Service Connection: Select the “mgmt vmmserver” service connection
  • Action: Start Virtual Machine
  • VM Names: $(vmname)
  • Wait Time: 180
    • Lowering this speeds up the process, not sure yet how low you can go. Default is 600.

Set IP Address on VM

To set an IP adress on the VM we first need to copy the script that can do that to the Hyper-V host which holds the VM, and then start powershell on the Hyper-V host to execute the script.

Copy Files to Hyper-V Host

First we need to add the proper PowerShell Script to the code repository. Go to Code → PowerShell folder. Creae a new file as setguestip.ps1 and add the following code:

Param(
   [string]$vmname,
   [string]$ipaddress,
   [string]$subnet,
   [string]$defaultgateway,
   [string]$dnsserver
)
 
# Script Variables
$scriptname = [System.IO.Path]::GetFilenameWithoutExtension($MyInvocation.MyCommand.Path.ToString())
$scriptlocation = Split-Path $myinvocation.mycommand.path
 
# Start transcript for full logging capabilities
# Note that when using TFS PS on Target machine only the output explicitly set in Write-Host will be written to this file
start-transcript -path "$scriptlocation\logtranscript.txt"
 
# Use this section when using the script in a TFS PowerShell task (not on a target machine) (add the required variables $mgmtuser,$mgmtpass)
# Note that sessions don't work in an unattended script so it should be run on the Hyper-V host
# mgmtpass is now a string so must be converted
# $mgmtpasssec = ConvertTo-SecureString -String $mgmtpass -AsPlainText -Force
# $mgmtcreds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $mgmtuser,$mgmtpasssec
 
# Use this section when using the script manually from your workstation
# $hostname = "developmentcluster-node1.management.getshifting.com"
# $mgmtcreds = Get-Credential
# $session = New-PSSession -Credential $mgmtcreds -ComputerName $hostname 
# Enter-PSSession $session
 
# Log the VMname which will be modified
write-host "VM = $vmname - Network = $ipaddress - $subnet - $defaultgateway - $dnsserver"
 
# Getting the required information from the Hyper-V Host
$VM = Get-WmiObject -Namespace 'root\virtualization\v2' -Class 'Msvm_ComputerSystem' | Where-Object { $_.ElementName -eq $vmname } 
$VMSettings = $vm.GetRelated('Msvm_VirtualSystemSettingData') | Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' }    
$VMNetAdapters = $VMSettings.GetRelated('Msvm_SyntheticEthernetPortSettingData') 
 
# Check if the VM object is empty. If so, the VM is probably running on a different host or something else is wrong
if ($vm -eq $null){
	write-error "Something went wrong requesting the VM information from the Hyper-V host. Exiting the script"
	Write-Host "##vso[task.logissue type=error;]Something went wrong requesting the VM information from the Hyper-V host."
	exit 1
}else{
	write-host "VM $($vm.elementname) is running on $($vm.pscomputername). VM version is $($vmsettings.version). NIC MAC Address is $($vmnetadapters.Address). "
}
 
# Check current IP address information for the VMs NIC
foreach($Adapter in $VMNetAdapters) {
	$nic = $Adapter.GetRelated("Msvm_GuestNetworkAdapterConfiguration") | Select IPAddresses, Subnets, DefaultGateways, DNSServers, DHCPEnabled, @{Name="AdapterName";Expression={$Adapter.ElementName}}
	write-host "VM IP info: IP: $($nic.IPAddresses). Subnet: $($nic.Subnets). DG: $($nic.DefaultGateways). DNS: $($nic.DNSServers). DHCP: $($nic.DHCPEnabled). AdapterName: $($nic.AdapterName)"
	}
 
$NetworkSettings = @()
$NetworkSettings = $NetworkSettings + $VMNetAdapters.GetRelated("Msvm_GuestNetworkAdapterConfiguration")
$NetworkSettings[0].IPAddresses = $ipaddress
$NetworkSettings[0].Subnets = $subnet
$NetworkSettings[0].DefaultGateways = $defaultgateway
# You can only set one DNS server. A second DNS server will have to be added using DHCP/GPO later on.
$NetworkSettings[0].DNSServers = $dnsserver
# Not sure what this is. 4098 seems to be set as default. 
$NetworkSettings[0].ProtocolIFType = 4096
$NetworkSettings[0].DHCPEnabled = $false
 
$Service = Get-WmiObject -Class "Msvm_VirtualSystemManagementService" -Namespace "root\virtualization\v2"
$setIP = $Service.SetGuestNetworkAdapterConfiguration($VM, $NetworkSettings[0].GetText(1))
write-host "vmsetip status = $($setip.returnvalue)"
 
# foutafhandeling
if ($setip.ReturnValue -eq 4096) {
	$job=[WMI]$setip.job 
	Write-host "Job status is $($job.jobstate)"
 
	while ($job.JobState -eq 3 -or $job.JobState -eq 4) {
		start-sleep 1
		$job=[WMI]$setip.job
		Write-host "Job status is $($job.jobstate)"
	}
 
	if ($job.JobState -eq 7) {
		write-host "Job Success"
	}
	else {
		$joberror = $job.GetError()
		write-host "Job error: $joberror"
		write-host "Job errorcode: $($job.errorcode)"
		write-host "Job error description: $($job.errordescription)"
		Write-Host "##vso[task.logissue type=warning;]Job error description: $($job.errordescription)"
		write-error "Setting the IP address failed for $vmname"
		Write-Host "##vso[task.logissue type=error;]Setting the IP address failed for $vmname"
		# Make sure the TFS tasks exits as failed
		exit 2
	}
} elseif($setip.ReturnValue -eq 0) {
	Write-Host "SetIP Success"
}
 
# Don't forget these when testing manually
#Exit-PSSession
#Remove-PSSession $session
Note that to see script output in the TFS logs you need to explicitly use write-host statements.
Note that in case you have a CI configured you can add the {***NO_CI*** comment to prevent the CI process to trigger.
Note that you can use Write-warning and Write-error to integrate powershell errors and warnings into the TFS build summary. The errors and warnings logged like that will be displayed in the TFS build summary as such.

Secondly we need to add a File Copy task. Click “+ Add Task” and search for “Windows Machine File Copy”. Click the “Windows Machine File Copy” task and click Add. Configure the task like this:

  • Display Name: Copy files from powershell/setguestip.ps1 (default, after selecting the correct files)
  • Source: powershell/setguestip.ps1
  • Machines: $(hostname)
  • Admin login: $(mgmtdomainautomationaccount)
  • Password: $(mgmtdomainautomationaccountpass)
  • Destination Folder: C:\tfsscripts

Remote PowerShell on Hyper-V Host

First we need to add a Remote PowerShell task. Click “+ Add Task” and search for “PowerShell on Target Machines”. Click the “PowerShell on Target Machines” task and click Add. Configure the task like this:

  • Display Name: Remote Powershell on Hyper-V
  • Machines: $hostname
  • Admin login: $(mgmtdomainautomationaccount)
  • Password: $(mgmtdomainautomationaccountpass)
  • Protocol: HTTP
    • When using HTTPS (my preferred choice) I got this error:
    • System.Management.Automation.Remoting.PSRemotingTransportException: Connecting to remote server <name of hyper-v host> failed with the following error message : WinRM cannot complete the operation. Verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled and allows access from this computer. By default, the WinRM firewall exception for public profiles limits access to remote computers within the same local subnet. For more information, see the about_Remote_Troubleshooting Help topic.
  • PowerShell Script: C:\tfsscripts\setguestip.ps1
  • Script Arguments: -vmname $(vmname) -ipaddress $(ipaddress) -subnet $(subnet) -defaultgateway $(defaultgateway) -dnsserver $(dnsserver)

Start VM Deployment

If you configured everything correctly you can noe queue a build by clicking Queue in the build definition.

Final Notes

  • The template being used has a blank administrator password. Yay for security, but that makes it impossible to automatically join the server to the AD domain. If required, a different template should be used.
  • If something goes wrong during the NewVM Action, the temporary Template that is created in the library is not removed. These typically can be identified like “ScvmmTaskTemplate_uniqueID”. These can be deleted.

Resources

You could leave a comment if you were logged in.
tfsscvmmnewvm.txt · Last modified: 2021/09/24 00:25 (external edit)