I don’t know if you are like me, but I’m a scripting nut. I figure that if I have to do something once, there is a fair chance I’ll have to do it again multiple times, sometimes months or even years later and I don’t want to have to bother to research it again, so why not simply script it now and sharpen my scripting skills in the process?
I also find that if you can script something, you have generally a better understanding of how it actually works as opposed to just pointing and clicking in a UI.
I started scripting using NT shell commands in Windows NT 4.0, and to this day I still have my beat up copy of Tim Hill’s « Windows NT Shell Scripting » which I use every now and then.
For a few years, I was also a hardcore Perl user and created all kinds of scripts for Windows and Active Directory management.
That was until PowerShell came out a few years ago. That scripting language is just something else. Once you start using it, you can’t stop. I’ve done tons of automation for Active Directory, Windows troubleshooting and VMware vSphere administration and reporting using the outstanding VMware vSphere PowerCLI extensions.
Which is why I’m really glad Nutanix came up with a bunch of PowerShell cmdlets that can help you automate operations on your Nutanix clusters. While those are relatively low touch solutions, there are certain features that can be tedious to setup for a large environment such as protection domains.
Others have already blogged about the Nutanix cmdlets, like Andre Leibovici‘s « Nutanix 4.0 PowerShell goes GA » or Kees Baggerman‘s excellent « Releasing the Nutanix Documentation Script », but I felt there wasn’t any good blog that took you through the process step by step and provided a few good examples of how to apply those cmdlets for true operational purposes. So here goes my two-cent.
Step 1 – Setting Nutanix PowerShell cmdlets up
First, let me start by saying that admittedly, the way the cmdlets have to be setup and used currently is not ideal. There isn’t a place on the web where you can simply download the cmdlets – you have to do it from Prism, the Nutanix web management interface.
Also, the built-in documentation of the cmdlets is somewhat lacking and you often have to dig into the support portal API reference document to get the cmdlets to do what you need them to do.
But enough ranting, let’s get down to business.
To install the cmdlets and get started:
- Connect to your Prism interface by opening the https://<cluster or CVM IP address>:9440/console/#login URL
- Click the Admin menu in the upper right corner and select Download Cmdlets Installer
- This downloads a NutanixCmdlets.msi file which you can then install on your Windows workstation or server
- You can then start the Nutanix cmdlets prompt by double clicking the NutanixCmdlets shortcut icon placed on your desktop
To double-check which version of the cmdlets you installed, use the following command:
Get-NTNXCmdletsInfo
…which should give you an output similar to the following:
Key Value --- ----- version 4.1.2 BuildVersion 1.1.2-release4.1.2-dev-02232015 RestAPIVersion v1
That’s it! Fairly painless so far.
Step 2 – Running your first commands interactively
Just like with most other PowerShell extensions, the process with Nutanix cmdlets is quite straightforward:
- You start by connecting to a Nutanix cluster
- You then use commands to display, create, delete or modify objects in that cluster
To connect to a Nutanix cluster, you need to use the Connect-NutanixCluster cmdlet as in the following example:
Connect-NutanixCluster -server $myvarNutanixCluster -username $myvarNutanixClusterUsername -password $myvarNutanixClusterPassword –AcceptInvalidSSLCerts
The server, username and password are just plain text. I just used variable names in the example because you would typically set those up beforehand, but you could just as easily type them in the same command.
[box type=”info”] Note: At the time of writing (July 2015), the cmdlets were not updated with NOS 4.1.3 or NOS 4.1.4, so you will get a warning stating that the cmdlets version does not match the cluster version. This is also preventing scripts from executing properly because that prompt cannot be worked around. This issue is actively being worked on and should be resolved in the 4.5 release. In the meantime, you can call Nutanix support and get an updated msi package to work around this issue.[/box]
Assuming you connected successfully, you should see something similar to this:
Server : 192.168.0.10 UserName : admin Password : ******** AcceptInvalidSSLCerts : True ParameterSetName : __AllParameterSets MyInvocation : System.Management.Automation.InvocationInfo PagingParameters : InvokeCommand : System.Management.Automation.CommandInvocationIntrinsics Host : System.Management.Automation.Internal.Host.InternalHost SessionState : System.Management.Automation.SessionState Events : System.Management.Automation.PSLocalEventManager JobRepository : System.Management.Automation.JobRepository JobManager : System.Management.Automation.JobManager InvokeProvider : System.Management.Automation.ProviderIntrinsics Stopping : False CommandRuntime : Connect-NutanixCluster CurrentPSTransaction : CommandOrigin : Runspace IsConnected : True
As long as IsConnected displays True, you’re good to go.
What can you do from here? Before we move into specific examples, the full documentation is available on the Nutanix support portal. If you have access to that portal, (it requires registration), then you’ll find the official documentation for the cmdlets in the API Reference document. More specifically, the “About Nutanix PowerShell Cmdlets” section explains how to get started and the “PowerShell Scrip Tutorial” helps you get going. The “PowerShell Cmdlets Reference” section gives you the full references needed for advanced scripting.
For a publicly available list of supported cmdlets, you can also check out the awesome Nutanix PowerShell cmdlet Reference Poster in PDF format.
Of course, when you are done, you should disconnect from the Nutanix cluster by using the following command:
Disconnect-NutanixCluster -servers $myvarNutanixCluster
Step 3 – Using the Nutanix PowerShell cmdlets in scripts
Typically in PowerShell, all you need to do is include an Import-Module or Add-PSSnapin statement, and this is no different with the Nutanix cmdlets.
All you need to include in your script to be able to use them are the following two lines of code:
$myvarLoaded = Get-PSSnapin -Name NutanixCmdletsPSSnapin -ErrorAction SilentlyContinue | % {$_.Name} if ($myvarLoaded -eq $null){Add-PSSnapin NutanixCmdletsPSSnapin}
After that, you simply add your Connect-NutanixCluster statement and off you go automating your tedious tasks.
I have included as an attachment a couple of PowerShell script templates that I use which includes those two lines as well as:
- Some extra error control when loading the snap-in
- Code to add the vSphere PowerCLI snap-in as well (for the template_vsphere.ps1 template)
- An OutputLogData function to handle the display of messages in script execution. The messages are color coded (green for informational messages, orange for warnings and red for errors) and optionally can be sent to a log file as well.
Feel free to re-use and/or modify it for your own purposes. The rest of the examples used in this blog have been created using that template.
Example 1 – Initializing storage on a freshly installed Nutanix cluster
In this example, we will create a storage pool and container on a freshly installed Nutanix cluster. The script also gives you the option simply to create a container with or without deduplication and/or compression just to show how that is done.
What the script does exactly:
- It connects to the Nutanix cluster you specified
- It creates a storage pool with a default or specified name. If the storage pool already exists, it gives you a warning message and continues.
- It creates a container with the options you specified (compression/deduplication)
- Optionally, it mounts the container as an NFS datastore on all the hosts in the Nutanix cluster, or on all the hosts in a given vSphere cluster
This script was tested on NOS 4.1.2 and ESXi 5.5 update 2. Note that it does only minimal error control.
<# .SYNOPSIS This script can be used to initialize storage on a freshly installed Nutanix cluster. .DESCRIPTION This script creates a storage pool and/or a container on a Nutanix cluster. .PARAMETER help Displays a help message (seriously, what did you think this was?) .PARAMETER history Displays a release history for this script (provided the editors were smart enough to document this...) .PARAMETER log Specifies that you want the output messages to be written in a log file as well as on the screen. .PARAMETER debugme Turns off SilentlyContinue on unexpected error messages. .PARAMETER cluster Nutanix cluster fully qualified domain name or IP address. .PARAMETER username Username used to connect to the Nutanix cluster. .PARAMETER password Password used to connect to the Nutanix cluster. .PARAMETER storagepool Name of the storage pool you want to create. If no name is specified, this will default to "<cluster name>-sp1". If a storage pool already exists, the script will not create a new one. .PARAMETER container Name of the container you want to create. If no name is specified, this will default to "<cluster name>-ct1". If a container already exists, the script will create a new one assuming the specified name is unique. .PARAMETER rf Specifies the replication factor (2 or 3; default used if not specified is 2). .PARAMETER compression Specifies that you want to enable compression on the container (default is not enabled). .PARAMETER compressiondelay Specifies the compression delay in seconds. If no compression delay is specified, then inline compression will be enabled (default is 0). .PARAMETER dedupe Specifies that you want to enable post process deduplication on the container (default is not enabled). .PARAMETER fingerprint Specifies that you want to enable inline fingerprinting on the container (default is not enabled). .PARAMETER connectnfs Specifies that you want to mount the new container as an NFS datastore on all ESXi hosts in the Nutanix cluster (default behavior will not mount the nfs datastore) .PARAMETER vcenter Hostname of the vSphere vCenter to which the hosts you want to mount the NFS datastore belong to. This is optional. By Default, if no vCenter server and vSphere cluster name are specified, then the NFS datastore is mounted to all hypervisor hosts in the Nutanix cluster. The script assumes the user running it has access to the vcenter server. .PARAMETER vcluster Name of the vSphere cluster with the hosts where you want to mount the NFS datastore. This is useful to specify if you have multiple compute clusters in your Nutanix cluster and you only want to mount your NFS datastore to the hosts of a specific compute cluster. .EXAMPLE Create a default storage pool and container on the specified Nutanix cluster: PS> .\add-NutanixStorage.ps1 -cluster ntnxc1.local -username admin -password admin .EXAMPLE Create a storage pool and container on the specified Nutanix cluster and enable inline compression on the container, then mount it on all ESXi hosts: PS> .\add-NutanixStorage.ps1 -cluster ntnxc1.local -username admin -password admin -storagepool spool1 -container compressed1 -compression -connectnfs .EXAMPLE Create a storage pool and container on the specified Nutanix cluster and mount the NFS datastore to the hosts that make up the vSphere cluster named "ntnxc1" and are managed by the vCenter server called "vcenter1": PS> .\add-NutanixStorage.ps1 -cluster ntnxc1.local -username admin -password admin -storagepool spool1 -container compressed1 -connectnfs -vcenter vcenter1.mydomain.local -vcluster ntnxc1 .LINK http://www.nutanix.com/services .NOTES Author: Stephane Bourdeaud (sbourdeaud@nutanix.com) Revision: July 22nd 2015 #> ###################################### ## parameters and initial setup ## ###################################### #let's start with some command line parsing Param ( #[parameter(valuefrompipeline = $true, mandatory = $true)] [PSObject]$myParam1, [parameter(mandatory = $false)] [switch]$help, [parameter(mandatory = $false)] [switch]$history, [parameter(mandatory = $false)] [switch]$log, [parameter(mandatory = $false)] [switch]$debugme, [parameter(mandatory = $true)] [string]$cluster, [parameter(mandatory = $true)] [string]$username, [parameter(mandatory = $true)] [string]$password, [parameter(mandatory = $false)] [string]$storagepool, [parameter(mandatory = $false)] [string]$container, [parameter(mandatory = $false)] [int]$rf, [parameter(mandatory = $false)] [switch]$compression, [parameter(mandatory = $false)] [int]$compressiondelay, [parameter(mandatory = $false)] [switch]$dedupe, [parameter(mandatory = $false)] [switch]$fingerprint, [parameter(mandatory = $false)] [switch]$connectnfs, [parameter(mandatory = $false)] [string]$vcenter, [parameter(mandatory = $false)] [string]$vcluster ) # get rid of annoying error messages if (!$debugme) {$ErrorActionPreference = "SilentlyContinue"} ######################## ## main functions ## ######################## #this function is used to output log data Function OutputLogData { #input: log category, log message #output: text to standard output <# .SYNOPSIS Outputs messages to the screen and/or log file. .DESCRIPTION This function is used to produce screen and log output which is categorized, time stamped and color coded. .NOTES Author: Stephane Bourdeaud .PARAMETER myCategory This the category of message being outputed. If you want color coding, use either "INFO", "WARNING", "ERROR" or "SUM". .PARAMETER myMessage This is the actual message you want to display. .EXAMPLE PS> OutputLogData -mycategory "ERROR" -mymessage "You must specify a cluster name!" #> param ( [string] $category, [string] $message ) begin { $myvarDate = get-date $myvarFgColor = "Gray" switch ($category) { "INFO" {$myvarFgColor = "Green"} "WARNING" {$myvarFgColor = "Yellow"} "ERROR" {$myvarFgColor = "Red"} "SUM" {$myvarFgColor = "Magenta"} } } process { Write-Host -ForegroundColor $myvarFgColor "$myvarDate [$category] $message" if ($log) {Write-Output "$myvarDate [$category] $message" >>$myvarOutputLogFile} } end { Remove-variable category Remove-variable message Remove-variable myvarDate Remove-variable myvarFgColor } }#end function OutputLogData ######################### ## main processing ## ######################### #check if we need to display help and/or history $HistoryText = @' Maintenance Log Date By Updates (newest updates at the top) ---------- ---- --------------------------------------------------------------- 06/19/2015 sb Initial release. ################################################################################ '@ $myvarScriptName = ".\add-NutanixStorage.ps1" if ($help) {get-help $myvarScriptName; exit} if ($History) {$HistoryText; exit} #let's load the Nutanix cmdlets if ((Get-PSSnapin -Name NutanixCmdletsPSSnapin -ErrorAction SilentlyContinue) -eq $null)#is it already there? { Add-PSSnapin NutanixCmdletsPSSnapin #no? let's add it if (!$?) #have we been able to add it successfully? { OutputLogData -category "ERROR" -message "Unable to load the Nutanix snapin. Please make sure the Nutanix Cmdlets are installed on this server." return } } #let's make sure the VIToolkit is being used if ($vcenter) { if ((Get-PSSnapin VMware.VimAutomation.Core -ErrorAction SilentlyContinue) -eq $null)#is it already there? { Add-PSSnapin VMware.VimAutomation.Core #no? let's add it if (!$?) #have we been able to add it successfully? { OutputLogData -category "ERROR" -message "Unable to load the PowerCLI snapin. Please make sure PowerCLI is installed on this server." return } } } #initialize variables #misc variables $myvarElapsedTime = [System.Diagnostics.Stopwatch]::StartNew() #used to store script begin timestamp $myvarvCenterServers = @() #used to store the list of all the vCenter servers we must connect to $myvarOutputLogFile = (Get-Date -UFormat "%Y_%m_%d_%H_%M_") $myvarOutputLogFile += "OutputLog.log" $myvarNutanixHosts = @() ############################################################################ # command line arguments initialization ############################################################################ #let's initialize parameters if they haven't been specified if (!$rf) {$rf = 2} #configure default rf (replication factor) if it has not been specified if (($rf -gt 3) -or ($rf -lt 2)) { OutputLogData -category "ERROR" -message "An invalid value ($rf) has been specified for the replication factor. It must be 2 or 3." break } if (!$fingerprint -and $dedupe) { OutputLogData -category "ERROR" -message "Deduplication can only be enabled when fingerprinting is also enabled." break } if ($compressiondelay -and !$compression) { OutputLogData -category "ERROR" -message "Compressiondelay can only be specified when compression is also used." break } if ($vcenter -and !$vcluster) { OutputLogData -category "ERROR" -message "You must specifiy a compute cluster name when you use the -vcenter parameter." break } ################################ ## Main execution here ## ################################ OutputLogData -category "INFO" -message "Connecting to Nutanix cluster $cluster..." if (!($myvarNutanixCluster = Connect-NutanixCluster -Server $cluster -UserName $username -Password $password –acceptinvalidsslcerts))#make sure we connect to the Nutanix cluster OK... {#error handling $myvarerror = $error[0].Exception.Message OutputLogData -category "ERROR" -message "$myvarerror" return } else #...otherwise show confirmation { OutputLogData -category "INFO" -message "Connected to Nutanix cluster $cluster." }#endelse if ($myvarNutanixCluster) { ###################### #main processing here# ###################### OutputLogData -category "INFO" -message "Creating the storage pool..." if (!$storagepool) { $storagepool = (Get-NTNXClusterInfo).Name + "-sp1" #figure out the cluster name and init default sp name } #add error control here to see if storage pool already exists if (!$storagepool -eq (Get-NTNXStoragePool | select -expand name)) { #create the container New-NTNXStoragePool -Name $storagepool -Disks (Get-NTNXDisk | select -ExpandProperty id) #figure out the storage pool id } else { OutputLogData -category "WARN" -message "The storage pool $storagepool already exists..." } $myvarStoragePoolId = get-ntnxstoragepool | where{$_.name -eq $storagepool} | select -expand id #need to add select id property only here OutputLogData -category "INFO" -message "Creating the container..." if (!$container) { $container = (Get-NTNXClusterInfo).Name + "-ct1" #figure out the cluster name and init default ct name } #add error control here to see if the container already exists if (!(get-ntnxcontainer | where{$_.name -eq $container})) { if ($compression -and $compressiondelay -and $dedupe -and $fingerprint) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -CompressionEnabled:$true -CompressionDelayInSecs $compressiondelay -FingerPrintOnWrite ON -OnDiskDedup POST_PROCESS) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } elseif ($compression -and $compressiondelay -and $dedupe) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -CompressionEnabled:$true -CompressionDelayInSecs $compressiondelay -OnDiskDedup POST_PROCESS ) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } elseif ($compression -and $compressiondelay -and $fingerprint) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -CompressionEnabled:$true -CompressionDelayInSecs $compressiondelay -FingerPrintOnWrite ON) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } elseif ($compression -and $compressiondelay) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -CompressionEnabled:$true -CompressionDelayInSecs $compressiondelay) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } elseif ($compression -and $dedupe -and $fingerprint) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -CompressionEnabled:$true -FingerPrintOnWrite ON -OnDiskDedup POST_PROCESS) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } elseif ($compression -and $fingerprint) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -CompressionEnabled:$true -FingerPrintOnWrite ON) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } elseif ($compression) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -CompressionEnabled:$true) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } elseif ($dedupe -and $fingeprint) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -FingerPrintOnWrite ON -OnDiskDedup POST_PROCESS) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } elseif ($dedupe) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -OnDiskDedup POST_PROCESS) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } elseif ($fingeprint) { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf -FingerPrintOnWrite ON) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } else { if (New-NTNXContainer -Name $container -StoragePoolId $myvarStoragePoolId -ReplicationFactor $rf) { OutputLogData -category "INFO" -message "Container $container was successfully created!" } } } else { OutputLogData -category "ERROR" -message "The container $container already exists..." break } if ($connectnfs) { if ($vcenter) { #connect to the vcenter server OutputLogData -category "INFO" -message "Connecting to vCenter server $vcenter..." if (!($myvarvCenterObject = Connect-VIServer $vcenter))#make sure we connect to the vcenter server OK... {#make sure we can connect to the vCenter server $myvarerror = $error[0].Exception.Message OutputLogData -category "ERROR" -message "$myvarerror" return } else #...otherwise show the error message { OutputLogData -category "INFO" -message "Connected to vCenter server $vcenter." }#endelse if ($myvarvCenterObject) { #get the hosts in the specified cluster $myvarESXiHosts = get-cluster $vcluster | get-vmhost | Get-VMHostNetworkAdapter -Name vmk0 | Select -ExpandProperty IP foreach ($myvarHost in $myvarESXiHosts) { #make sure all hosts in the compute cluster are also in the Nutanix cluster and get their unique IDs $myvarNutanixHosts += Get-NTNXHost | where {$_.hypervisorAddress -eq $myvarHost} | select -ExpandProperty serviceVMId OutputLogData -category "INFO" -message "Found ESXi host with IP address $myvarHost in the Nutanix cluster $cluster..." } } #mount the NFS datastore to the specified hosts if ($myvarNutanixHosts) { Add-NTNXNfsDatastore -DatastoreName $container -ContainerName $container -NodeIds $myvarNutanixHosts | Out-Null OutputLogData -category "INFO" -message "Mounted datastore $container to the ESXi hosts in $vcluster" } else { OutputLogData -category "ERROR" -message "Could not find any host to mount the NFS datastore to in $vcluster..." } OutputLogData -category "INFO" -message "Disconnecting from vCenter server $vcenter..." Disconnect-viserver -Confirm:$False #cleanup after ourselves and disconnect from vcenter } else { OutputLogData -category "INFO" -message "Mounting the NFS datastore $container on all hypervisor hosts in the Nutanix cluster..." Add-NTNXNfsDatastore -DatastoreName $container -ContainerName $container | Out-Null } } }#endif OutputLogData -category "INFO" -message "Disconnecting from Nutanix cluster $cluster..." Disconnect-NutanixCluster -Servers $cluster #cleanup after ourselves and disconnect from the Nutanix cluster ######################### ## cleanup ## ######################### #let's figure out how much time this all took OutputLogData -category "SUM" -message "total processing time: $($myvarElapsedTime.Elapsed.ToString())" #cleanup after ourselves and delete all custom variables Remove-Variable myvar* Remove-Variable ErrorActionPreference Remove-Variable help Remove-Variable history Remove-Variable log Remove-Variable cluster Remove-Variable username Remove-Variable password Remove-Variable storagepool Remove-Variable container Remove-Variable compression Remove-Variable compressiondelay Remove-Variable dedupe Remove-Variable fingerprint Remove-Variable connectnfs Remove-Variable rf Remove-Variable vcenter Remove-Variable vcluster Remove-Variable debugme
Example 2 – Setup a protection domain and consistency group for vSphere virtual machines in a given folder
Often, virtualization administrators group virtual machines that make up an application in one or more specific folder(s). Consistency groups in Nutanix enable taking synchronized crash-consistent snapshots for a group of virtual machines. Therefore, it makes sense to be able to create consistency groups based on how your virtual machines have been organized in folders in vCenter.
Specifically, the script will:
- Let you specify a vCenter server and a Nutanix cluster, along with one or more VM folder name(s). Scheduling options for the protection domain must also be specified as well as a retention policy for snapshots. You can also specify an interval in minutes if you are processing multiple folders and you would like to spread out the schedules of the protection domains snapshot operations.
- Create a protection domain on the Nutanix cluster named after each VM folder name.
- Create a consistency group in that protection domain also named after each VM folder and add all the VMs in that folder to that consistency group.
- Create a schedule for each protection domain using the local container as a target and at the specified date and time.
- Set the specified retention policy on each protection domain.
- Optionally, replicate once immediately so that your VMs are protected (use with caution when you have lots of consistency groups).
Note that the script does not cover VSS integration.
This script was tested on NOS 4.1.2 and ESXi 5.5 update 2. Note that it does only minimal error control.
<# .SYNOPSIS This script can be used to create protection domains and consistency groups based on a VM folder structure in vCenter. .DESCRIPTION This script creates protection domains with consistency groups including all VMs in a given vCenter server VM folder. Protection domains and consistency groups are automatically named "<clustername>-pd-<foldername>" and "<clustername>-cg-<foldername>". .PARAMETER help Displays a help message (seriously, what did you think this was?) .PARAMETER history Displays a release history for this script (provided the editors were smart enough to document this...) .PARAMETER log Specifies that you want the output messages to be written in a log file as well as on the screen. .PARAMETER debugme Turns off SilentlyContinue on unexpected error messages. .PARAMETER cluster Nutanix cluster fully qualified domain name or IP address. .PARAMETER username Username used to connect to the Nutanix cluster. .PARAMETER password Password used to connect to the Nutanix cluster. .PARAMETER vcenter Hostname of the vSphere vCenter to which the hosts you want to mount the NFS datastore belong to. This is optional. By Default, if no vCenter server and vSphere cluster name are specified, then the NFS datastore is mounted to all hypervisor hosts in the Nutanix cluster. The script assumes the user running it has access to the vcenter server. .PARAMETER folder Name of the VM folder object in vCenter which contains the virtual machines to be added to the protection domain and consistency group. You can specify multiple folder names by separating them with commas in which case you must enclose them in double quotes. .PARAMETER repeatEvery Valid values are HOURLY, DAILY and WEEKLY, followed by the number of repeats. For example, if you want backups to occur once a day, specify "DAILY,1" (note the double quotes). .PARAMETER startOn Specifies the date and time at which you want to start the backup in the format: "MM/dd/YYYY,HH:MM". Note that this should be in UTC znd enclosed in double quotes. .PARAMETER retention Specifies the number of snapshot versions you want to keep. .PARAMETER replicateNow This is an optional parameter. If you use -replicateNow, a snapshot will be taken immediately for each created consistency group. .PARAMETER interval This is an optional parameter. Specify the interval in minutes at which you want to separate each schedule. This is to prevent scheduling all protection domains snapshots at the same time. If you are processing multiple folders, the first protection domain will be scheduled at the exact specified time (say 20:00 UTC), the next protection domain will be scheduled at +interval minutes (so 20:05 UTC if your interval is 5), and so on... .EXAMPLE Create a protection domain for VM folders "appA" and "appB", schedule a replication every day at 8:00PM UTC, set a retention of 3 snapshots and replicate immediately: PS> .\add-NutanixProtectionDomains.ps1 -cluster ntnxc1.local -username admin -password admin -vcenter vcenter1 -folder "appA,appB" -repeatEvery "DAILY,1" -startOn "07/29/2015,20:00" -retention 3 -replicateNow .LINK http://www.nutanix.com/services .NOTES Author: Stephane Bourdeaud (sbourdeaud@nutanix.com) Revision: July 29th 2015 #> ###################################### ## parameters and initial setup ## ###################################### #let's start with some command line parsing Param ( #[parameter(valuefrompipeline = $true, mandatory = $true)] [PSObject]$myParam1, [parameter(mandatory = $false)] [switch]$help, [parameter(mandatory = $false)] [switch]$history, [parameter(mandatory = $false)] [switch]$log, [parameter(mandatory = $false)] [switch]$debugme, [parameter(mandatory = $true)] [string]$cluster, [parameter(mandatory = $true)] [string]$username, [parameter(mandatory = $true)] [string]$password, [parameter(mandatory = $true)] [string]$vcenter, [parameter(mandatory = $true)] [string]$folder, [parameter(mandatory = $true)] [string]$repeatEvery, [parameter(mandatory = $true)] [string]$startOn, [parameter(mandatory = $true)] [string]$retention, [parameter(mandatory = $false)] [switch]$replicateNow, [parameter(mandatory = $false)] [int]$interval ) # get rid of annoying error messages if (!$debugme) {$ErrorActionPreference = "SilentlyContinue"} ######################## ## main functions ## ######################## #this function is used to output log data Function OutputLogData { #input: log category, log message #output: text to standard output <# .SYNOPSIS Outputs messages to the screen and/or log file. .DESCRIPTION This function is used to produce screen and log output which is categorized, time stamped and color coded. .NOTES Author: Stephane Bourdeaud .PARAMETER myCategory This the category of message being outputed. If you want color coding, use either "INFO", "WARNING", "ERROR" or "SUM". .PARAMETER myMessage This is the actual message you want to display. .EXAMPLE PS> OutputLogData -mycategory "ERROR" -mymessage "You must specify a cluster name!" #> param ( [string] $category, [string] $message ) begin { $myvarDate = get-date $myvarFgColor = "Gray" switch ($category) { "INFO" {$myvarFgColor = "Green"} "WARNING" {$myvarFgColor = "Yellow"} "ERROR" {$myvarFgColor = "Red"} "SUM" {$myvarFgColor = "Magenta"} } } process { Write-Host -ForegroundColor $myvarFgColor "$myvarDate [$category] $message" if ($log) {Write-Output "$myvarDate [$category] $message" >>$myvarOutputLogFile} } end { Remove-variable category Remove-variable message Remove-variable myvarDate Remove-variable myvarFgColor } }#end function OutputLogData ######################### ## main processing ## ######################### #check if we need to display help and/or history $HistoryText = @' Maintenance Log Date By Updates (newest updates at the top) ---------- ---- --------------------------------------------------------------- 06/19/2015 sb Initial release. ################################################################################ '@ $myvarScriptName = ".\add-NutanixProtectionDomains.ps1" if ($help) {get-help $myvarScriptName; exit} if ($History) {$HistoryText; exit} #let's load the Nutanix cmdlets if ((Get-PSSnapin -Name NutanixCmdletsPSSnapin -ErrorAction SilentlyContinue) -eq $null)#is it already there? { Add-PSSnapin NutanixCmdletsPSSnapin #no? let's add it if (!$?) #have we been able to add it successfully? { OutputLogData -category "ERROR" -message "Unable to load the Nutanix snapin. Please make sure the Nutanix Cmdlets are installed on this server." return } } #let's make sure the VIToolkit is being used if ((Get-PSSnapin VMware.VimAutomation.Core -ErrorAction SilentlyContinue) -eq $null)#is it already there? { Add-PSSnapin VMware.VimAutomation.Core #no? let's add it if (!$?) #have we been able to add it successfully? { OutputLogData -category "ERROR" -message "Unable to load the PowerCLI snapin. Please make sure PowerCLI is installed on this server." return } } #initialize variables #misc variables $myvarElapsedTime = [System.Diagnostics.Stopwatch]::StartNew() #used to store script begin timestamp $myvarvCenterServers = @() #used to store the list of all the vCenter servers we must connect to $myvarOutputLogFile = (Get-Date -UFormat "%Y_%m_%d_%H_%M_") $myvarOutputLogFile += "OutputLog.log" ############################################################################ # command line arguments initialization ############################################################################ #let's initialize parameters if they haven't been specified $myvarFolders = $folder.Split("{,}") if ($interval -and (($interval -le 0) -or ($interval -ge 60))) { OutputLogData -category "ERROR" -message "Interval must be between 1 and 59 minutes!" break } ################################ ## Main execution here ## ################################ OutputLogData -category "INFO" -message "Connecting to Nutanix cluster $cluster..." if (!($myvarNutanixCluster = Connect-NutanixCluster -Server $cluster -UserName $username -Password $password –acceptinvalidsslcerts))#make sure we connect to the Nutanix cluster OK... {#error handling $myvarerror = $error[0].Exception.Message OutputLogData -category "ERROR" -message "$myvarerror" return } else #...otherwise show confirmation { OutputLogData -category "INFO" -message "Connected to Nutanix cluster $cluster." }#endelse if ($myvarNutanixCluster) { #connect to the vcenter server OutputLogData -category "INFO" -message "Connecting to vCenter server $vcenter..." if (!($myvarvCenterObject = Connect-VIServer $vcenter))#make sure we connect to the vcenter server OK... {#make sure we can connect to the vCenter server $myvarerror = $error[0].Exception.Message OutputLogData -category "ERROR" -message "$myvarerror" return } else #...otherwise show the error message { OutputLogData -category "INFO" -message "Connected to vCenter server $vcenter." }#endelse if ($myvarvCenterObject) { ###################### #main processing here# ###################### $myvarLoopCount = 0 foreach ($myvarFolder in $myvarFolders) { #let's make sure the protection domain doesn't already exist $myvarPdName = (Get-NTNXClusterInfo).Name + "-pd-" + $myvarFolder if (Get-NTNXProtectionDomain -Name $myvarPdName) { OutputLogData -category "WARN" -message "The protection domain $myvarPdName already exists! Skipping to the next item..." continue } #retrieve list of VMs in the specified folder OutputLogData -category "INFO" -message "Retrieving the names of the VMs in $myvarFolder..." $myvarVMs = Get-Folder -Name $myvarFolder | get-vm | select -ExpandProperty Name if (!$myvarVMs) {#no VM in that folder... OutputLogData -category "WARN" -message "No VM object was found in $myvarFolder or that folder was not found! Skipping to the next item..." continue } #create the protection domain OutputLogData -category "INFO" -message "Creating the protection domain $myvarPdName..." Add-NTNXProtectionDomain -Input $myvarPdName | Out-Null #create the consistency group $myvarCgName = (Get-NTNXClusterInfo).Name + "-cg-" + $myvarFolder OutputLogData -category "INFO" -message "Creating the consistency group $myvarCgName..." Add-NTNXProtectionDomainVM -Name $myvarPdName -ConsistencyGroupName $myvarCgName -Names $myvarVMs | Out-Null #################### #create the schedule #################### #let's parse the repeatEvery argument (exp format: DAILY,1) $myvarType = ($repeatEvery.Split("{,}"))[0] $myvarEveryNth = ($repeatEvery.Split("{,}"))[1] #let's parse the startOn argument (exp format: MM/dd/YYYY,HH:MM in UTC) $myvarDate = ($startOn.Split("{,}"))[0] $myvarTime = ($startOn.Split("{,}"))[1] $myvarMonth = ($myvarDate.Split("{/}"))[0] $myvarDay = ($myvarDate.Split("{/}"))[1] $myvarYear = ($myvarDate.Split("{/}"))[2] $myvarHour = ($myvarTime.Split("{:}"))[0] $myvarMinute = ($myvarTime.Split("{:}"))[1] #let's figure out the target date for that schedule if ($interval -and ($myvarLoopCount -ge 1)) {#an interval was specified and this is not the first time we create a schedule $myvarTargetDate = (Get-Date -Year $myvarYear -Month $myvarMonth -Day $myvarDay -Hour $myvarHour -Minute $myvarMinute -Second 00 -Millisecond 00).AddMinutes($interval * $myvarLoopCount) } else {#no interval was specified, or this is our first time in this loop withna valid object $myvarTargetDate = Get-Date -Year $myvarYear -Month $myvarMonth -Day $myvarDay -Hour $myvarHour -Minute $myvarMinute -Second 00 -Millisecond 00 } $myvarUserStartTimeInUsecs = [long][Math]::Floor((($myvarTargetDate - (New-Object DateTime 1970, 1, 1, 0, 0, 0, ([DateTimeKind]::Utc))).Ticks / [timespan]::TicksPerSecond)) * 1000 * 1000 #let's create the schedule OutputLogData -category "INFO" -message "Creating the schedule for $myvarPdName to start on $myvarTargetDate UTC..." Add-NTNXProtectionDomainCronSchedule -Name $myvarPdName -Type $myvarType -EveryNth $myvarEveryNth -UserStartTimeInUsecs $myvarUserStartTimeInUsecs | Out-Null #configure the retention policy OutputLogData -category "INFO" -message "Configuring the retention policy on $myvarPdName to $retention..." Set-NTNXProtectionDomainRetentionPolicy -pdname ((Get-NTNXProtectionDomain -Name $myvarPdName).Name) -Id ((Get-NTNXProtectionDomainCronSchedule -Name $myvarPdName).Id) -LocalMaxSnapshots $retention | Out-Null if ($replicateNow) { #replicate now OutputLogData -category "INFO" -message "Starting an immediate replication for $myvarPdName..." Add-NTNXOutOfBandSchedule -Name $myvarPdName | Out-Null } ++$myvarLoopCount } } }#endif OutputLogData -category "INFO" -message "Disconnecting from Nutanix cluster $cluster..." Disconnect-NutanixCluster -Servers $cluster #cleanup after ourselves and disconnect from the Nutanix cluster OutputLogData -category "INFO" -message "Disconnecting from vCenter server $vcenter..." Disconnect-viserver -Confirm:$False #cleanup after ourselves and disconnect from vcenter ######################### ## cleanup ## ######################### #let's figure out how much time this all took OutputLogData -category "SUM" -message "total processing time: $($myvarElapsedTime.Elapsed.ToString())" #cleanup after ourselves and delete all custom variables Remove-Variable myvar* Remove-Variable ErrorActionPreference Remove-Variable help Remove-Variable history Remove-Variable log Remove-Variable cluster Remove-Variable username Remove-Variable password Remove-Variable folder Remove-Variable repeatEvery Remove-Variable startOn Remove-Variable retention Remove-Variable replicateNow Remove-Variable vcenter Remove-variable interval Remove-Variable debugme
EDIT (August 2016): I was asked to produce a script to enable existing protection domain editing by specifying VM names or part of VMs name. I have included the source code below.
<# .SYNOPSIS This script can be used to add unprotected vm(s) to an existing protection domain on Nutanix. .DESCRIPTION This script adds existing virtual machines to an existing protection domain if they are not already protected. .PARAMETER help Displays a help message (seriously, what did you think this was?) .PARAMETER history Displays a release history for this script (provided the editors were smart enough to document this...) .PARAMETER log Specifies that you want the output messages to be written in a log file as well as on the screen. .PARAMETER debugme Turns off SilentlyContinue on unexpected error messages. .PARAMETER cluster Nutanix cluster fully qualified domain name or IP address. .PARAMETER username Username used to connect to the Nutanix cluster. .PARAMETER password Password used to connect to the Nutanix cluster. .PARAMETER pd Name of the protection domain you want to add vms to. .PARAMETER vm Name of the virtual machine(s) you want to add. You can specify mulitple vm names by use a comma separated list enclosed in double quotes or using the * wildcard. Note that when you use the * wildcard, it will try to match that string (exp: *mystring or mystring* or my*string will always behave as *mystring*) .PARAMETER replicateNow This is an optional parameter. If you use -replicateNow, a snapshot will be taken immediately for each created consistency group. .EXAMPLE Add all VMs that start with myvm* to protection domain "mypd" and replicate immediately: PS> .\add-NutanixProtectionDomains.ps1 -cluster ntnxc1.local -username admin -password admin -pd mypd -vm myvm* -replicateNow .LINK http://www.nutanix.com/services .NOTES Author: Stephane Bourdeaud (sbourdeaud@nutanix.com) Revision: August 18th 2016 #> ###################################### ## parameters and initial setup ## ###################################### #let's start with some command line parsing Param ( #[parameter(valuefrompipeline = $true, mandatory = $true)] [PSObject]$myParam1, [parameter(mandatory = $false)] [switch]$help, [parameter(mandatory = $false)] [switch]$history, [parameter(mandatory = $false)] [switch]$log, [parameter(mandatory = $false)] [switch]$debugme, [parameter(mandatory = $true)] [string]$cluster, [parameter(mandatory = $true)] [string]$username, [parameter(mandatory = $true)] [string]$password, [parameter(mandatory = $true)] [string]$pd, [parameter(mandatory = $true)] [string]$vm, [parameter(mandatory = $false)] [switch]$replicateNow, [parameter(mandatory = $false)] [int]$interval ) # get rid of annoying error messages if (!$debugme) {$ErrorActionPreference = "SilentlyContinue"} ######################## ## main functions ## ######################## #this function is used to output log data Function OutputLogData { #input: log category, log message #output: text to standard output <# .SYNOPSIS Outputs messages to the screen and/or log file. .DESCRIPTION This function is used to produce screen and log output which is categorized, time stamped and color coded. .NOTES Author: Stephane Bourdeaud .PARAMETER myCategory This the category of message being outputed. If you want color coding, use either "INFO", "WARNING", "ERROR" or "SUM". .PARAMETER myMessage This is the actual message you want to display. .EXAMPLE PS> OutputLogData -mycategory "ERROR" -mymessage "You must specify a cluster name!" #> param ( [string] $category, [string] $message ) begin { $myvarDate = get-date $myvarFgColor = "Gray" switch ($category) { "INFO" {$myvarFgColor = "Green"} "WARNING" {$myvarFgColor = "Yellow"} "ERROR" {$myvarFgColor = "Red"} "SUM" {$myvarFgColor = "Magenta"} } } process { Write-Host -ForegroundColor $myvarFgColor "$myvarDate [$category] $message" if ($log) {Write-Output "$myvarDate [$category] $message" >>$myvarOutputLogFile} } end { Remove-variable category Remove-variable message Remove-variable myvarDate Remove-variable myvarFgColor } }#end function OutputLogData ######################### ## main processing ## ######################### #check if we need to display help and/or history $HistoryText = @' Maintenance Log Date By Updates (newest updates at the top) ---------- ---- --------------------------------------------------------------- 08/18/2016 sb Initial release. ################################################################################ '@ $myvarScriptName = ".\add-VmToPd.ps1" if ($help) {get-help $myvarScriptName; exit} if ($History) {$HistoryText; exit} #let's load the Nutanix cmdlets if ((Get-PSSnapin -Name NutanixCmdletsPSSnapin -ErrorAction SilentlyContinue) -eq $null)#is it already there? { Add-PSSnapin NutanixCmdletsPSSnapin #no? let's add it if (!$?) #have we been able to add it successfully? { OutputLogData -category "ERROR" -message "Unable to load the Nutanix snapin. Please make sure the Nutanix Cmdlets are installed on this server." return } } #initialize variables #misc variables $myvarElapsedTime = [System.Diagnostics.Stopwatch]::StartNew() #used to store script begin timestamp $myvarvCenterServers = @() #used to store the list of all the vCenter servers we must connect to $myvarOutputLogFile = (Get-Date -UFormat "%Y_%m_%d_%H_%M_") $myvarOutputLogFile += "OutputLog.log" #initialize the array variable we are going to use to store vm objects to add to the protection domain #[System.Collections.ArrayList]$myvarVMsToAddToPd = New-Object System.Collections.ArrayList($null) $myvarVMsToAddToPd = @() ############################################################################ # command line arguments initialization ############################################################################ #let's initialize parameters if they haven't been specified $myvarVMs = $vm.Split("{,}") ################################ ## Main execution here ## ################################ OutputLogData -category "INFO" -message "Connecting to Nutanix cluster $cluster..." if (!($myvarNutanixCluster = Connect-NutanixCluster -Server $cluster -UserName $username -Password $password –acceptinvalidsslcerts -ForcedConnection))#make sure we connect to the Nutanix cluster OK... {#error handling $myvarerror = $error[0].Exception.Message OutputLogData -category "ERROR" -message "$myvarerror" return } else #...otherwise show confirmation { OutputLogData -category "INFO" -message "Connected to Nutanix cluster $cluster." }#endelse if ($myvarNutanixCluster) { ###################### #main processing here# ###################### #start by making sure the protection domain exists OutputLogData -category "INFO" -message "Getting protection domain $pd..." if (Get-NTNXProtectionDomain -Name $pd) { $myvarPdObject = Get-NTNXProtectionDomain -Name $pd #take the array of vm names specified by the user and process each entry foreach ($myvarVM in $myvarVMs) { if ($myvarVM -match '\*') { #strip wildcard $myvarVM = $myvarVM -replace '\*' #retrieve vms using search if ($myvarSearchedVMs = Get-NTNXVM -SearchString $myvarVM) { #process each retrieved entry foreach ($myvarSearchedVM in $myvarSearchedVMs) { #check protection status if ($myvarSearchedVM.protectionDomainName) { #warn that vm is already protected and move on OutputLogData -category "WARN" -message "VM $($myvarSearchedVM.vmName) is already in protection domain $($myvarSearchedVM.protectionDomainName)..." continue } else #the vm is not in a protection domain { #add vm to the list OutputLogData -category "INFO" -message "Adding VM $($myvarSearchedVM.vmName) to the list of VMs to add to protection domain $pd..." $myvarVMsToAddToPd += $myvarSearchedVM.vmName } }#end foreach searched vm } else #we did not find any matching VM { OutputLogData -category "WARN" -message "Could not find any VM matching string $myvarVM..." } } else #the vm name did not contain a wildcard { #retrieve the vm object if ($myvarExactVM = Get-NTNXVM | Where-Object {$_.vmName -eq $myvarVM}) { #check that vm protection status if ($myvarExactVM.protectionDomainName) { #warn that vm is already protected and move on OutputLogData -category "WARN" -message "VM $($myvarExactVM.vmName) is already in protection domain $($myvarExactVM.protectionDomainName)..." continue } else #the vm is not in a protection domain { #add vm to the list OutputLogData -category "INFO" -message "Adding VM $($myvarExactVM.vmName) to the list of VMs to add to protection domain $pd..." $myvarVMsToAddToPd += $myvarExactVM.vmName } } else { OutputLogData -category "WARN" -message "Could not find VM $($myvarExactVM.vmName)..." } }#endif contains wildcard }#end foreach vm #add vms to the pd if ($myvarVMsToAddToPd) { OutputLogData -category "INFO" -message "Adding VMs to protection domain $pd..." Add-NTNXProtectionDomainVM -Name $pd -Names $myvarVMsToAddToPd | Out-Null }#endif vms to add to pd? } else { $myvarerror = $error[0].Exception.Message OutputLogData -category "ERROR" -message "$myvarerror" break } if ($replicateNow) { #replicate now OutputLogData -category "INFO" -message "Starting an immediate replication for the protection domain $pd..." Add-NTNXOutOfBandSchedule -Name $pd | Out-Null } } OutputLogData -category "INFO" -message "Disconnecting from Nutanix cluster $cluster..." Disconnect-NutanixCluster -Servers $cluster #cleanup after ourselves and disconnect from the Nutanix cluster ######################### ## cleanup ## ######################### #let's figure out how much time this all took OutputLogData -category "SUM" -message "total processing time: $($myvarElapsedTime.Elapsed.ToString())" #cleanup after ourselves and delete all custom variables Remove-Variable myvar* Remove-Variable ErrorActionPreference Remove-Variable help Remove-Variable history Remove-Variable log Remove-Variable cluster Remove-Variable username Remove-Variable password Remove-Variable pd Remove-Variable vm Remove-Variable replicateNow Remove-Variable debugme
For more coding examples (including in other languages such as Python), make sure to check out the Nutanix GitHub repository.
Conclusion
Using PowerShell is easy, and so is using the Nutanix PowerShell cmdlets. While the Prism UI is very intuitive and easy to use, sometimes you just don’t want to click 100 times for something a script can get done in a few seconds. Now you have all the tools required to automate your environment, so why don’t you get started?
Let me know what you come up with and if you hit any problem.
Don’t forget to leverage the almighty Nutanix Community as well and once you’ve unleashed your scripting superpower, enter our Total Recode coding challenge for a chance to win a 4000$ drone, a 2500$ home lab or 2000$ in cash! Now who said coding skills couldn’t make you rich?
[…] PowerCLI and the Nutanix cmdlets to be installed. For instructions on how to set that up, refer to this blog […]