Scripts

Welcome to the Nutanix NEXT community. To get started please read our short welcome post. Thanks!

cancel
Showing results for 
Search instead for 
Did you mean: 

Powershell based Management for VDI deployments

Highlighted
Explorer

Powershell based Management for VDI deployments

Hey there,

 

Just a little something that should help with a mass deployment of VMs for use with a VDI implementation.

 

In order to run this script you will need to execute it from a powershell session that has the commandlets loaded.

 

I have leveraged the Posh-SSH module to handle my SSH sessions, however this is not set in stone, and a little modification can easily be done to utlize a preferred module instead.

 

This code is by no means complete or secure. I have tried to include security mesasure where I can by at least cleaning the command history of the current powershell session on the successful exit from the script. However there is still some work to be done to make this script a little more secure. ie: masking passwords from command history.

 

There are some considerations to take into account in order for this script to be utilized succesfully. Some operations will inherently take a long time to complete fully, and thus intential delays have been included into the script to allow for things to function in a more reasonible fashion. The start-up of VMs has been intentionally been staggered with a specified sleep delay between each boot up operation to allow for a more controlled boot storm.

 

Another concideration to take into accont, while waiting for some operations to complete the powershell session will remain dormant/inactive, in this state the SSH session to the cluster will timeout and become stale. When this happens a refresh of the session will need to be done. Sometime you will need to also reconnect to the cluster before the VM operations will function correctly. If neither of these help exit the script and close the current powershell session and open a new NutanixCmdlets powershell session. This is a little annoying however it does the trick.

 

This script make the rapid deployment/destruction of VDI desktops from powershell possible.
Guest/hypervisor power operations are available.
Joining the VDI desktops onto a domain is also supported (As long as the base prerequisites are covered)
   VDI Desktop prerequisites:
   Windows 7 SP1
   DotNet 4.x
   Powershell v3.0+
   Windows Remote Management (WinRM) service started and allowing connections from all hosts.

 

<#
.SYNOPSIS
   Manage basic VMs tasks on a Nutanix cluster.
   Power on/off
   Guest reset/shutdown
   Clone from VM/Snapshot
   Delete VM
   Join VMs to Windows domain
.DESCRIPTION
   This script make the rapid deployment/destruction of VDI desktops possible.
   Guest/hypervisor level power operations are also covered.
   Joining the VDI desktops onto a domain is also supported (As long as the base prerequisites are covered)

   VDI Desktop prerequisites:
   Windows 7 SP1
   DotNet 4.x
   Powershell v3.0+
   Windows Remote Management (WinRM) service started and allowing connections from all hosts. (This setting can be overwritten once a computer joins the domain, where a GPO can enforce more strict rules).
.PARAMETER
   None. This is an interactive script requiring user input for the desired results.
.EXAMPLE
   vdi_admin.ps1
#>
##
# DATE: 25/08/2015
# AUTHOR: Ryan Leih 
##
# Global variables
##
# Sleep duration in seconds
$sleep = 10;
# Choose SSH module
$moduleName = 'Posh-SSH';
# Cluster connection status
$status = 0;
# Cluster connection status message
$statusMessage = 'Not Connected';
# SSH module status
$sshStatus = 0;
$sshCreds = '';
$secSSHCreds = '';
$sshStream = '';
$choice = '';
$clusterIP = '';
$clusterSSHip ='';
$user = '';
$pass = '';
$secLocalCreds = '';
$localCreds = '';
$domain = '';
$secDomCreds = '';
$domCreds = '';
$nutanixUser = 'nutanix';
$nutanixPass = '';
$vmPrefix = '';
$vms = '';
$clonePrefix = '';
$cloneTemp = '';
$cloneSnap = '';
$cloneAmount = '';
$cloneStart = '';


# Download and install the PoshSSH commandlets for use with this session.
Function installSSH
{
	iex (New-Object Net.WebClient).DownloadString("https://gist.github.com/darkoperator/6152630/raw/c67de4f7cd780ba367cccbc2593f38d18ce6df89/instposhsshdev");
	Write-Host 'Press any key to continue...';
	Read-Host;
	# Set status of module to TRUE
	$sshStatus = 1;
	mainMenu;
}

# Import the PoshSSH commandlets.
# Place the modules under C:\Windows\System32\WindowsPowerShell\v1.0\Modules for a more permanent solution
Function importSSH
{
	# If the Posh-SSH module has not been loaded
	if (-not(Get-Module -name $moduleName))
	{
		# Load the SSH module
		Import-Module -Name $moduleName;
	}
	else
	{
		# Display message stating module has been loaded
		Write-Host 'Module already loaded...';
		# Set status of module to TRUE
		$sshStatus = 1;
	}

	# Check if modules has been loaded and set flags accordingly
	checkModule;
}

# Verify if the module has been loaded and is available to utilize
Function checkModule
{
	# Check if the module has in fact been loaded
	if (Get-Module -name $moduleName)
	{
		Write-Host 'Module loaded...';
		# Set SSH module status to TRUE
		$sshStatus = 1;
	}
	else
	{
		# Ensure module status is FALSE
		$sshStatus = 0;
	}
	# Return to the main menu
	mainMenu;
}

# Connect to a specific Nutanix cluster
Function clusterConnect
{
	# Get IP address of cluster
	$clusterIP = Read-Host 'Enter cluster IP address: ';
	# Administrative user to log onto cluster
	$user = Read-Host 'Enter Nutanix cluster username';
	# Password for admin user
	$pass = Read-Host 'Enter Nutanix' $user 'password';
	Write-Host 'Logging onto cluster with provided credentials...';
	# Try connect to cluster. If successful return to 
	try
	{
		Connect-NutanixCluster -Server $clusterip -UserName $user -Password $pass -AcceptInvalidSSLCert;
		$status = 1;
		$statusMessage = 'Connected to ' + $clusterIP;
		mainMenu;
	}
	catch [System.Management.Automation.RuntimeException]
	{
		# Ensure cluster connected status is reflected as Not Connected
		$status = 0;
		$statusMessage = 'Not Connected';
		# Display message stating that the login failed
		Write-Host 'Login failed';
		Write-Host 'Press any key to continue...';
		Read-Host;
		# Return to main menu
		mainMenu;
	}
}

# Initialize an SSH session to the Nutanix cluster
Function startSSHsession
{
	# Get cluster IP address
	$clusterSSHip = Read-Host 'Enter cluster IP address: ';
	# Get password for nutanix user
	$nutanixPass = Read-Host 'Enter password for nutanix user';
	# Create SSH credential object
	$secSSHCreds = ConvertTo-SecureString $nutanixPass -AsPlainText -Force
	# Create new SSH connection using login credentials
	$sshCreds = New-Object System.Management.Automation.PSCredential ($nutanixUser,$secSSHCreds)
	# Start a new SSH session stream
	New-SSHSession -ComputerName $clusterSSHip -Credential $sshCreds
	# Open session stream
	connectSSH;
}

# Initialize the SSH stream to start sending commands to the host
Function connectSSH
{
	# Open new SSH stream to start sending commands to host
	$sshStream = New-SSHShellStream -Index 0
	mainMenu;
}

# Power On all VMs based on the prefix specified
Function powerOn
{
	if ($vmPrefix -eq '')
	{
		# Notify user that additional data is required
		Write-Host 'Please specify VM prefix before trying to continue';
		# Wait for user input
		Read-Host;
	}
	else
	{
		# Get all VMs that match the prefix specified
		$vms = Get-NTNXVM | Where {$_.vmName -like $vmPrefix};
		# Loop over each VM in the list and power on the VM
		foreach ($vm in $vms)
		{
			# Craft out the command to run
			$command = "acli vm.on " + $vm.vmName
			# Send command to host to execute
			$sshStream.WriteLine($command)
			# Wait X seconds before booting the next VM
			Sleep $sleep;
		}
	}
	# Return to VM operations sub-menu
	vmOps;
}

# Power Off all VMs based on the prefix specified
Function powerOff
{
	if ($vmPrefix -eq '')
	{
		# Notify user that additional data is required
		Write-Host 'Please specify VM prefix before trying to continue';
		# Wait for user input
		Read-Host;
	}
	else
	{
		# Get all VMs that match the prefix specified
		$vms = Get-NTNXVM | Where {$_.vmName -like $vmPrefix};
		# Loop over each VM in the list and power off the VM
		foreach ($vm in $vms)
		{
			# Craft out the command to run
			$command = "acli vm.off " + $vm.vmName
			# Send command to host to execute
			$sshStream.WriteLine($command)
		}
	}
	# Return to VM operations sub-menu
	vmOps;
}


# Gracefully Reboot all VMs based on the prefix specified
Function rebootGuestOS
{
	if ($vmPrefix -eq '')
	{
		# Notify user that additional data is required
		Write-Host 'Please specify VM prefix before trying to continue';
		# Wait for user input
		Read-Host;
	}
	else
	{
		# Get all VMs that match the prefix specified
		$vms = Get-NTNXVM | Where {$_.vmName -like $vmPrefix};
		# Loop over each VM in the list and power off the VM
		foreach ($vm in $vms)
		{
			# Craft out the command to run
			$command = "acli vm.reboot " + $vm.vmName
			# Send command to host to execute
			$sshStream.WriteLine($command)
		}
	}
	# Return to VM operations sub-menu
	vmOps;
}


# Gracefully Power Off VMs based on the prefix specified
Function powerOffOS
{
	if ($vmPrefix -eq '')
	{
		# Notify user that additional data is required
		Write-Host 'Please specify VM prefix before trying to continue';
		# Wait for user input
		Read-Host;
	}
	else
	{
		# Get all VMs that match the prefix specified
		$vms = Get-NTNXVM | Where {$_.vmName -like $vmPrefix};
		# Loop over each VM in the list and power off the VM
		foreach ($vm in $vms)
		{
			# Craft out the command to run
			$command = "acli vm.shutdown " + $vm.vmName
			# Send command to host to execute
			$sshStream.WriteLine($command)
		}
	}
	# Return to VM operations sub-menu
	vmOps;
}

# Clone X number of VMs based on a template VM, starting from Y
Function cloneVMTemp
{
	# Get prefix used to identify VMs
	$clonePrefix = Read-Host 'Enter clone VM prefix';
	# Get number of clones to create
	$cloneAmount = Read-Host 'Enter number of clones to create';
	# Get starting clone number
	$cloneStart = Read-Host 'Enter the starting number of the clone [0-999]';
	# Get template name to clone from
	$cloneSnap = Read-Host 'Enter the name of the VM template to clone from';
	# Calculate the end number for the cloned VM
	$cloneAmount = [int]$cloneStart + [int]$cloneAmount;
	# Loop over max amount
	for($i=[int]$cloneStart; $i -lt [int]$cloneAmount; $i++)
	{
		# Name of VM if < 10
		if ($i -lt 10) {
			$command = "echo yes | acli vm.clone " + $clonePrefix + "00" + $i + " clone_from_vm=" + $cloneSnap;
		}
		# Name of VM if >= 10 < 100
		elseif ($i -lt 100) {
			$command = "echo yes | acli vm.clone " + $clonePrefix + "0" + $i + " clone_from_vm=" + $cloneSnap;
		}
		# Name of VM if >= 100
		else {
			$command = "echo yes | acli vm.clone " + $clonePrefix + $i + " clone_from_vm=" + $cloneSnap;
		}
		# Start the cloning operation for a single VM
		$sshStream.WriteLine($command)
	}
	# Return to VM operations sub-menu
	vmOps;
}

# Clone X number of VMs based on a snapshot, starting from Y
Function cloneVMSnap
{
	# Get prefix used to identify VMs
	$clonePrefix = Read-Host 'Enter clone VM prefix';
	# Get number of clones to create
	$cloneAmount = Read-Host 'Enter number of clones to create';
	# Get starting clone number
	$cloneStart = Read-Host 'Enter the starting number of the clone [0-999]';
	# Get snap name to clone from
	$cloneSnap = Read-Host 'Enter the name of the snapshot to clone from';
	# Calculate the end number for the cloned VM
	$cloneAmount = [int]$cloneStart + [int]$cloneAmount;
	# Loop over max amount
	for($i=[int]$cloneStart; $i -lt [int]$cloneAmount; $i++)
	{
		# Name of VM if < 10
		if ($i -lt 10) {
			$command = "echo yes | acli vm.clone " + $clonePrefix + "00" + $i + " clone_from_snapshot=" + $cloneSnap;
		}
		# Name of VM if >= 10 < 100
		elseif ($i -lt 100) {
			$command = "echo yes | acli vm.clone " + $clonePrefix + "0" + $i + " clone_from_snapshot=" + $cloneSnap;
		}
		# Name of VM if >= 100
		else {
			$command = "echo yes | acli vm.clone " + $clonePrefix + $i + " clone_from_snapshot=" + $cloneSnap;
		}
		# Start the cloning operation for a single VM
		$sshStream.WriteLine($command)
	}
	# Return to VM operations sub-menu
	vmOps;
}

# Destroy VMs basd on the prefix specified
Function destroyVMs
{
	if ($vmPrefix -eq '')
	{
		# Notify user that additional data is required
		Write-Host 'Please specify VM prefix before trying to continue';
		# Wait for user input
		Read-Host;
	}
	else
	{
		# Get all VMs that match the prefix specified
		$vms = Get-NTNXVM | Where {$_.vmName -like $vmPrefix};
		# Loop over each VM in the list and power off the VM
		foreach ($vm in $vms)
		{
			# Craft out the command to run
			# Send yes command to the delete command to avoid having to type yes for each request
			$command = "echo yes | acli vm.delete " + $vm.vmName
			# Send command to host to execute
			$sshStream.WriteLine($command)
		}
	}
	# Return to VM operations sub-menu
	vmOps;
}

# Gather details used for the VM operations
Function gatherVM
{
	# Get prefix used to identify VMs
	$vmPrefix = Read-Host 'Enter VM prefix';
	# Add wildcard to the end of the prefix
	$vmPrefix = $vmPrefix + '*';
	# Return to VM operations sub-menu
	vmOps;
}

# Run command on VMs that will rename and join the computer to specified domain
Function joinDomain
{
	if ($vmPrefix -eq '')
	{
		# Notify user that additional data is required
		Write-Host 'Please specify VM prefix before trying to continue';
		# Wait for user input
		Read-Host;
	}
	else
	{
		# Get the local administrator user account name
		$localUser = Read-Host 'Enter the local administrator user account name';
		# Get the password for the local admin account of the VM to connect to
		$secLocalPass = Read-Host 'Please enter the password for the local administrator account';
		$secLocalCreds = ConvertTo-SecureString $secLocalPass -AsPlainText -Force;
		# Create credential object that we will leverage to authenticate against the remote computer
		$localCreds = New-Object System.Management.Automation.PSCredential ($localUser,$secLocalCreds);
		# Get the name of the domain
		$domain = Read-Host 'Please specify domain name eg: test.local';
		# Get domain account name with join domain privileges
		Write-Host 'Enter domain user account with join domain privileges';
		$domUser = Read-Host 'Specify domain and user eg: test\user';
		$domPass = Read-Host 'Please enter the password for the domain account';
		# Get list of VMs that match the VM prefix
		$vms = Get-NTNXVM | Where {$_.vmName -like $vmPrefix};

		foreach ($vm in $vms)
		{
			$vmName = $vm.vmName;
			Invoke-Command -ComputerName $vm.ipAddresses[0] -Credential $localCreds -ScriptBlock {param($name,$user,$pass) $secDomCreds = ConvertTo-SecureString $pass -AsPlainText -Force; $domCreds = New-Object System.Management.Automation.PSCredential ($user,$secDomCreds); Add-Computer -DomainName test.local -Credential $domCreds -newname $name} -ArgumentList $vmName,$domUser,$domPass;
		}
	}
	# Return to VM operations sub-menu
	vmOps;
}

<#
## Function currently disabled ##
# Gather details used for the VM cloning operations
Function gatherClone
{
	# Get prefix used to identify VMs
	$clonePrefix = Read-Host 'Enter clone VM prefix';
	# Get number of clones to create
	$cloneAmount = Read-Host 'Enter number of clones to create';
	# Get starting clone number
	$cloneStart = Read-Host 'Enter the starting number of the clone [0-999]';
}
#>

# Display the VM operations sub-menu
Function vmOps
{
	# Clear the screen
	cls;
	# Print menu options
	Write-Host 'Cluster status: ' $statusMessage;

	# Display the status of the SSH module
	if ($sshStatus)
	{
		Write-Host -ForegroundColor GREEN 'SSH module status: ENABLED';
	}
	else
	{
		Write-Host -ForegroundColor RED 'SSH module status: DISABLED';
	}

	# Display the status of the VM information
	if ($vmPrefix -eq '')
	{
		Write-Host -ForegroundColor RED 'No VM information specified';
	}
	else
	{
		Write-Host -ForegroundColor GREEN 'VM prefix set:' $vmPrefix;
	}

	Write-Host '';
	Write-Host '';
	Write-Host '1: Power on VMs';
	Write-Host '2: Power off VMs (forcefully)';
	Write-Host '3: Reboot guest OS';
	Write-Host '4: Power off guest OS';
	Write-Host '5: Clone VMs based on template';
	Write-Host '6: Clone VMs based on snapshot';
	Write-Host '7: Destroy VMs (without confirmation)';
	Write-Host '11: Get VM details';
	Write-Host '22: Join VMs to domain';
	Write-Host '99: Exit';
	Write-Host '';
	# Get users choice
	$choice = Read-Host 'Choose an option';
	# Call corresponding function based on user's input
	switch ($choice)
	{
		# Power on VMs
		1 {powerOn}
		# Power off VMs (forcefully)
		2 {powerOff}
		# Reboot guest OS
		3 {rebootGuestOS}
		# Power off guest OS
		4 {powerOffOS}
		# Clone VMs based on template
		5 {cloneVMTemp}
		# Clone VMs based on snapshot
		6 {cloneVMSnap}
		# Destroy VMs (without confirmation)
		7 {destroyVMs}
		# Gather details for VM operations
		11 {gatherVM}
		# Join Windows VMs to domain
		22 {joinDomain}
		# Exit to main menu
		99 {mainMenu;}
		default { Write-Host 'Please enter a valid option.'; Read-Host; vmOps;}
	}
}

# This function is used to exit the script cleanly
Function dismissed
{
	#This is a blank function used to exit the script in a clean fashion
}

# The main function for this script
Function mainMenu
{
	# Clear the screen
	cls;
	# Print menu options
	Write-Host 'Cluster status: ' $statusMessage;

	# Display the status of the SSH module
	if ($sshStatus)
	{
		Write-Host -ForegroundColor GREEN 'SSH module status: ENABLED';
	}
	else
	{
		Write-Host -ForegroundColor RED 'SSH module status: DISABLED';
	}

	Write-Host '';
	Write-Host '';
	Write-Host '1: Install Posh-SSH';
	Write-Host '2: Import Posh-SSH';
	Write-Host '3: Connect to cluster';
	Write-Host '4: Connect to cluster using SSH';
	Write-Host '5: Refresh stale SSH session';
	Write-Host '6: VM operations';
	Write-Host '99: Exit';
	Write-Host '';
	# Get users choice
	$choice = Read-Host 'Choose an option';
	# Call corresponding function based on user's input
	switch ($choice)
	{
		# Install SSH module
		1 {installSSH}
		# Load the SSH module
		2 {importSSH}
		# Connect to the cluster
		3 {clusterConnect}
		# Initialize an SSH session to the cluster
		4 {startSSHsession}
		# Refresh a stale/disconnected SSH session
		5 {connectSSH}
		# Display VM operations sub-menu
		6 {vmOps}
		# Controlled exit to command prompt
		99 {dismissed;}
		default { Write-Host 'Please enter a valid option.'; Read-Host; mainMenu;}
	}
}

# The entry into the script
# Display the main menu
mainMenu;

# Exit from the script
# Clear the console typed history, for security reasons Smiley Wink
[system.reflection.assembly]::loadwithpartialname("System.Windows.Forms")
[System.Windows.Forms.SendKeys]:Smiley Frustratedendwait('%{F7 2}')
# Clear the screen
cls;