Category Archives: Scripting

PowerCLI Session 01: Variables, Arrays

Variable Definitions

Welcome to our first lesson in PowerCLI. Today we’ll be getting our hands on  the second most-used thing after commandlets, and these are variables. You will be using them to temporarily store information that you will either use as an input for your subsequent commands (or store commands themselves!), or save them to a text file, maybe even a report.

The variables are always initialized, called, and saved with a dollar sign preceeding a string of your choice. For example $a, $var1, $myvariable, $esxihost etc.

Feeding a value to a variable is pretty straight forward – it is done with an equal sign.

$var1 = 9001

Now, a variable can contain more than just numbers and strings, for example objects, arrays, imported CSVs and hashtables. You can also force a certain data type to be used in your variable.

Variable Definition Code Examples

Since this is a first lesson, I’d like to say that commenting your code properly and clearly is the very fundamental thing to do. This helps you orient in the code, and if you decide to share it – provide some insight for other people who might be using or analyzing your scripts.

Commenting a single line is done via hashtag # and commenting multiple lines of code is used by closing the hashtags in less/greater than signs like this: <# #>

With all of the above, let me show you how variables are defined:


# Let's start with a simple string
$string1 = "Hello World!"
# And then writing your first Hello World to the console.
Write-Host $string1

# You can also force a variable type for your arrays
# Find more info on data types here: http://ss64.com/ps/syntax-datatypes.html
[boolean]$istrue = false
[int]$volume = 5000

# This is a definition an empty array...
$arr1 = @()

# ...and a hashtable
$hash1 = @{}

<# Now, a single variable can contian many things,
like objects and commands. The below stores an
array of objects - all ESXi hosts inside a vCenter#>

$vmhosts = Get-VMHost 

$csv1 = Import-CSV .\myreport.csv

# You can store a command and then subsequently run it with invoke-expression!
$mycommand = "Get-VM"
Invoke-Expression $mycommand

Arrays

Now that you know how to define variables, let’s take a look at Arrays. Hashtables will be covered in one of future sessions, they are quite advanced and I’d like to maintain a coherent way of showing you around.

Arrays are pretty self-explanatory -you can store variables and even objects inside them. One important thing to remember is, that if you have filled an array with objects via a command, you can not prune (remove objects from) the array manually. All the objects need to be explicitly defined by a filter when you push them into an array (we shall look at that in the next session), or you can create an empty array and then push your objects there.

After you store data in an array, you an list it – as you can do with all variables – by simply entering its variable name into the powershell prompt.01

Arrays are indexed with brackets [], starting from 0. So you can see ther “0th” entry like this:02

The individual members of the whole array can also be listed, so as you were shown in the first lesson, using the Get-Member, or gm command in a pipe (pipes will be a subject of our next session). You can also fill an array with a CSV file if you have more parameters you wish to extract from your array (which will become handy later)

This is for just a short overview of the arrays – you will get much more familiar with them in a more hands-on approach. See you around!

 

Threaded Report Generator: VMs without a vmxnet3 vmnic

Yesterday I worked on a nice-to-have script for our Team. Since I had some time on my hands, I decided to give it a take from a different angle and make it threaded. I was fiddling around and googling like crazy, but eventually after several failed attempts and debugging sessions it paid off. I did a huge step in scripting for myself, and I’d like to present the script to you! Threading is something I wanted to learn for a long time, so I am very happy I am able to share this piece of code.

What the script basically does is grab a list of vCenters either via an array (the line can be modified to get it via get-content instead), and then creates a thread (aka a Background Job) for each of them, using the ScriptBlock that is defined in $getnics variable. You can take a look at it and it somewhat closely resembles a function. With one exception – the ScriptBlock for this kind of job (having pristine CSV values) must be completely silent except the last step – returning the $csv value.

When all jobs finish via Get-Job | Wait-Job, all the variables are aggregated into one master variable $csv with Receive-Job commandlet. This retrieves the output contents of the job but also marks them in a way that you couldn’t retrieve these contents again (the variable will be blank), so extra caution is needed there. In the end it all gets outputted to C:\temp\ as a CSV with forced semicolon separator.

In the end you are shown the total time in seconds the script took to run – in our environment of roughly 4500 VMs across all vCenters, it took about 1s per VM non-threaded. The threaded run was completed in ~700 seconds, which is a nice speed boost 🙂 The sample of how the report looks is below:

Sample Report

A report generated with columns for vCenter, its VM, the NIC Type and Guest OS.

 

Write-Host "Threaded vmnic type Gatherer " -ForeGroundColor Cyan
$startedtime = Get-Date

# get date to present to the .csv file
$date = Get-Date -format ddMMyy-HHmm

# Initialize CSV Here
$csv = @()
$csv += "sep=;"
$csv += "vCenter;VM;NIC Type;Guest OS"

# List of vCenters where to gather data from, can be replaced with get-content.
$vcenterlist = @("vcenter1","vcenter2")

# Initialize ScriptBlock to be used in Threads
$getnics= {	# The curly bracket is needed to initiate ScriptBlock
	Param([String]$vc)  # A vCenter will be an input parameter
	Process {	# Start the ScriptBlock's work here:
	$WarningPreference = "SilentlyContinue" # Disable warnings
	[array]$csv = $null # Initialize empty CSV Array

	# Since PowerCLI SnapIn isn't present in Threading, we need to load it
	add-pssnapin VMware.VimAutomation.Core -ea 0 | out-null

	Connect-VIServer $vc -wa 0 | out-null # Connect to vCenter, Silently

	$AllVMs = get-vm # Gather Virtual Machines...

		# Do a ForEach Object (%) on each VM to find out wich VMnic they have
		$AllVMs | % {

		$ThisVM = $_
		$vmnic = (Get-NetworkAdapter -VM $ThisVM).Type

		# No vmxnet3 NICs, no NULL values and no dummy SMVI machines.
	If ($vmnic -ne 'vmxnet3' -and $vmnic -ne $null) # If vmnic isn't vmxnet3 and isn't null, then:
		{
		$guestos = $ThisVM.Guest.OSFullName
		$csv += "$vc;$ThisVM;$vmnic;$guestos"
		}

		}
		return ,$csv

	# Disconnect from vCenter
	Disconnect-VIServer $vc -wa 0 -confirm:$false | out-null
	}
}

# Start threads and impose a wait time so the CPU on the terminal doesn't go crazy.
forEach ($vc in $vcenterlist) {

Write-Host "Starting Thread for" $vc -NoNewLine
Start-Job -Scriptblock $getnics -ArgumentList $vc | out-null
Write-Host "...Done!" -ForegroundColor "Green"
Start-Sleep -s 15

}

Write-Host "All threads started! Waiting for them to finish." -Foregroundcolor Green

# List jobs and wait until they are all completed
Get-Job | Wait-Job 

# Gather data from Threads by aggregating each job into a master CSV variable.
Write-Host "Gathering data from the threads..."
$jobs = Get-Job | ? {$_.State -eq "Completed"}
ForEach ($job in $jobs) {
	$csv += Receive-job $job
}

# Write CSV File
$csvpath = "C:\temp\vmnicreport_$date.csv"
Write-Host "Writing CSV file to $csvpath"
$csv | Out-File $csvpath

# Final stats
$endedtime = Get-Date
$timetook = ($endedtime - $startedtime).TotalSeconds

# Cleanup
Get-Job | Remove-Job

Write-Host "Finished in" $timetook "seconds!" -ForegroundColor Green

I hope you will find this script useful one day when deciding which VMs to upgrade – but don’t forget to update VMware tools first before you get into it, so your VM has sustained connectivity after a reboot.

PowerCLI Session 00: Introduction to PowerShell & PowerCLI

A few words to start with

Welcome to PowerCLI Sessions, where I’ll be showing you how VMware’s PowerShell Module PowerCLI works, along with examples so you can analyze, study, and most conveniently use the knowledge you find to your own good. I’d like to start with a “0th” session, to introduce people to PowerShell-based scripting on Windows Server systems.

Starting with Windows Server 2008, Microsoft has included an object-oriented shell to its systems called PowerShell. This was to quickly replace the previous, widely used scripting engine own to the Windows OS Family, Visual Basic. Whereas I have done several Visual Basic scripts in the past, it was frankly cumbersome to work with (and I guess many of you guys who have had the pleasure of scripting in VB will agree) – debugging was quite hard, the mnemonics were not that easy to remember – in short it did its job but you had to spend some time fiddling with the code as IDEs were next to none for this scripting language.

Introducing PowerShell & PowerCLI Module

With PowerShell, your weapons in the arsenal are the so-called commandlets (or cmdlets for short), and they are almost always based on a verb-noun basis, so they are pretty easy to remember. The outputs are almost always objects with various properties (or members) that are incredibly useful as I will be showing you throughout the lessons. You will need to wrap your mind around the fact that you are now working with objects in a shell environment – no more feeding plain strings everywhere. Sometimes you will need to input an object into your command else it will fail.

How does PowerCLI come into play here then? Simply by being a Module (we can also call it a plugin, extension, etc.) for PowerShell – supplying it with many new commandlets to be used exclusively within a VMware environment – all these commandlets operate by communicating with vCenter Server, or the ESXi host itself.

Installation

First things first – make sure your core PowerShell is updated to the most recent version possible (v3 for Windows 2008 R2 and v4 for Windows Server 2012 onwards) You can get the PowerCLI Version 5.5 in VMware’s repository – you will just need an account at my.vmware.com. The installation is pretty simple. Just download the executable, follow the instructions and then launch the console with a shortcut that has been created either in your start menu or on the Desktop.

Getting warmed up

The simplest command you will use in PowerCLI is Connect-VIServer (notice the Verb-Noun mnemonic?). This will establish either a connection to the vCenter or ESXi host. If you are unsure how to use the command, just try to Get-Help for it.gethelp

Now you are ready to run the command.

connectvcenter

From there you can use Get-VMHost to list all your ESXi hosts connected to the vCenter Server, Get-VM to get all the Virtual Machines.getvmhost

To explore each object’s properties, a VERY useful tool is Get-Member – this will show you what else is hiding behind the values that were just listed. Let’s try it with Get-VMHostgetmember

As you have noticed I have used the pipe and a shortcut to this command which is gm – and you will be using a lot while learning about objects’ members. To explore a member of the first position in the array, you use a dot like this – let’s use it to check the build number of the first ESXi host:

getmember-buld

To disconnect from the vCenter Server or the ESXi host, just type in Disconnect-VIServer servername and you are done.

Congratulations! You have just tapped in the awesome world of PowerCLI – we’ll continue with introduction to variables in the next Session.

Host IPMI Event Status Alarm PowerCLI Fix

Sometimes we are affected by a (supposedly firmware) bug that rarely affects our ESXi hosts in vCenter. This happens mainly on HP BL460c blade servers. You will get an alarm with IPMI Event Log Status, Other Hardware Objects, or Hardware Temperature status, but everything will appear okay on the Hardware Status screen and the IPMI log will be clear (or show you that 65535 are present when they aren’t). The gathered information has  pinpointed me towards resetting the CIM Service and Hardware Monitoring agents.

What this handy PowerCLI Script does is basically everything described above, but without tne hassle of connecting to each host manually – it’s a bit modular so take a look on the code first if you understand the mnemonics, if not just run it, enter your vCenter and VM name (don’t forget to see the $hosts value – it should contain your domain’t name!) and wait for a moment.

Write-Host "Reset sensors, refresh HW data & their views on an ESXi host" `n -ForegroundColor Green

<# Uncomment this to enable connection to vCenters defined in a text file
# 

$vcenterlist = Get-Content .\vcenters.txt

ForEach ($vcenter in $vcenterlist) {
Define vCenter
Write-Host `n"Connecting to vCenter $vcenter"

# Connect to vCenter
Connect-VIServer $vcenter | Out-Null
}
#>

# Define a blank array for the hosts
$hosts = @()

# input checking loop to check if $vcenter is null or not.
if ($vcenterlist -eq $null) {
do  {

[Boolean]$match = $false
$vcenter = Read-Host "Define a vCenter where the host is located"
$vcenter.Replace(' ','')
if ($vcenter -notlike "") { $match = $true }

Else {
Write-Host "The value must not be null. Try again or CTRL+C to break."`n -ForegroundColor Red
$match = $false
}

} Until ($match -eq $true)
}

# ESXi host definition
$input = Read-Host "Enter a name of ESXi host where you want to reset the HW sensors"
# Generate FQDN and store into an Array
$hosts += "$input`.yourdomain.lab"

# Connect to vCenter
Write-Host `n "Connecting to vCenter $vcenter`..."
ForEach ($vcenter in $vcenterlist) {
Connect-VIServer $vcenter | Out-Null
}

# The VMhost needs to be stored into an array with Get-VMhost for further processing
$vmhosts = Get-VMHost -Name $hosts

# Get all vmhosts for the connected vCenter sessions
#$vmhosts = Get-VMHost

ForEach ($vmhost in $vmhosts)
{
	Try
	{
		#initialize calls for refreshing hardware status..
		Write-Host "Restarting CIM Server service on $vmhost"
		Get-VMHost $vmhost | Get-VMHostService | Where { $_.Key -eq “sfcbd-watchdog” } | Restart-VMHostService -Confirm:$false | Out-Null
		Start-Sleep -Seconds 15

		Write-Host "Starting to refresh HW info on $vmhost (this can take a while)"

		# Define variables for system calls
		$hv = Get-View $vmhost
		$hss = get-view $hv.ConfigManager.HealthStatusSystem

		Write-Host "Resetting HW Sensors..."
		$hss.ResetSystemHealthInfo()
		Start-Sleep -Seconds 15

		Write-Host "Refreshing Data..."
		$hss.RefreshHealthStatusSystem()
		Start-Sleep -Seconds 15

		Write-Host "Refreshing Data View..."
		$hss.UpdateViewData()
		Start-Sleep -Seconds 15
	}
	Catch [System.Exception]
	{
		Write-Host "There was an error while trying to refresh the hardware data." `n `
					"Please check the ESXi host's Hardware Status Tab." -ForegroundColor 'Red'
	}
	Finally
	{
		Write-Host "Disconnecting from the vCenter Server..."
		Disconnect-VIServer $vcenter -Confirm:$false
		Write-Host "Done processing $vmhost." -ForegroundColor Green
	}
}

I Hope it has alleviated at least one occurrence of this bug 🙂

Scripting Corner: NetApp Storage Controller Option Setter

Last week I have introduced to you NetApp Storage Controller Options Retriever – now it is the time to show you how to push a bulk amount of options to the NetApp Storage Controller via PowerShell. As with the previous post, you will need the Data OnTap PowerShell Toolkit.

The input of this script is a .csv file containing Option and Value columns – these must match exactly the options you wish to set and a valid value going along with it. The next imput is controllers.txt where your controllers you want to apply options to are written, with line break as a separator. A sample .csv is shown below:

Option;Value
cf.takeover.on_failure;on
cf.takeover.on_network_interface_failure;off
cf.giveback.auto.delay.seconds;600

And below is the script itself:

# The Module DataOntap is needed for this script to function.

# Define a function to push the given option
function Push-NaOption ($option, $value)
{
	Try
	{
		Set-NaOption -OptionName $option -OptionValue $value | Out-Null
	}
	Catch
	{
	Write-Host There was an error setting the $option on $sc. -ForegroundColor 'red'
	}
}

Write-Host `n "--- NetApp Option Pusher ---" -ForegroundColor 'Cyan' `n
$startedtime = Get-Date

# Generate a new array for controller list and fill it
$sclist = @()
$sclist += Get-Content controllers.txt

# Do the same with the options array
$CSV = Import-Csv optionstopush.csv

Write-Host 'Started pushing options to' $sclist.count 'Storage Controllers' `n

# Initialize failed controllers array
$failedcount = 0
$failednames = @()

# Start pushing the options
ForEach ($sc in $sclist)
{
	# Verify if the connection has been successful, if not increase the failed counter and add the name to array.
	Try
	{
		Connect-NaController $sc -ErrorAction 'SilentlyContinue' | Out-Null
		Write-Host "Processing Controller $sc" -ForegroundColor Green
		if ($global:CurrentNaController -eq $null)
		{
			Write-Host "Could not connect to Controller $sc ! Skipping..." -ForegroundColor Red
			$failedcount++
			$failednames += $sc
			Continue
		}

	}
	# Also catch an exception if it was thrown.
	Catch
	{
		Write-Host "Could not connect to Controller $sc ! Skipping..." -ForegroundColor Red
		$failedcount++
		$failednames += $sc
		Continue
	}

	ForEach ($line in $CSV)
	{
		Push-NaOption $line.Option $line.Value
	}

}

# Count statistics...
$endedtime = Get-Date
$timetook = ($endedtime - $startedtime).TotalSeconds

$scOK = $sclist.count - $failedcount

# Final stat listing.
Write-Host `n Processed $scOK Storage Controllers 'in' $timetook seconds. -ForegroundColor Green
If ($failedcount -ge 1)
{
	Write-Host `n "Connection to the following $failedcount controllers failed: " $failednames `n -ForegroundColor 'Red'
}

Hope this saved you some headache trying to push any amount of options to a large number of NetApp Storage Controllers.

Scripting Corner: NetApp Storage Controller Options Retriever

Once upon a time we had to make a revision of some options on about 50 NetApp Storage Controllers in our environment. The options were many and we needed to retrieve them in a reviewable fashion to mark out which comply to NetApp’s best practices.

You will require NetApp’s Data OnTap Powershell Toolkit in order for this script to run, because it uses their commandlets.

It took me four days to devise this script, mainly because I had other things to do like resolving incidents and doing actual work 😀 The hardest part was the looping and writing to CSV. This script has a few caveats:

  • If you search for just one option, the CSV will not be generated properly, so enter a random second option, input the same one or just an asterisk will do (the fields will be blank).
  • It is better if you take the CSV and invert it – better readability and searchability, however I have realized that after the script has been finished. And I’d rather not return to the cycling nightmare again 🙂
  • The CSV is generated procedurally inside a variable and is output to a file at the end of the script – if you stop it while running halfway, you get no partial CSV.

The sample output can  be found below:

A sample output from options collected by the script - not transposed.

A sample output from options collected by the script – not transposed.

You will need two input files – controllers.txt with the controllers you want to query and options.txt with options you wish to retrieve – the separator is a line break. Please note that the options must be input exactly as they appear in Data OnTap, else the script can misbehave. I hope many of you will find this script useful.

Import-Module DataONTAP

Write-Host `n "--- NetApp Option Retriever ---" -ForegroundColor 'Cyan' `n
$startedtime = Get-Date

#Define path to the exported CSV.
$csvpath1 = ".\options.csv"

# Generate a new array for controller list and fill it
$sclist = @()
$sclist += Get-Content controllers.txt

# Do the same with the options array
$optIN = @()
$optIN += Get-Content options.txt

Write-Host 'Started gathering' $OptIN.count 'options on' $sclist.count 'Storage Controllers' `n

# A base for list of options we want to find
$optcmd = "Get-NaOption | Sort | Select Name, Value"

# Create an array from the options we want to find by first sorting it out...
$optIN = $optIN | Sort

# ...And then formatting it like a CSV
$datasuffix = @("Data;")

# Fill in the first column of the CSV
foreach ($item in $optIN)
{
	$datasuffix += "$item" + ";"
}

# Intialize the array with first column
$CachedCsv = @()
$CachedCsv += $datasuffix

# Initialize empty array for the ignored fields
$ignored = @()

# Initialize failed filers value
$failedcount = 0
$failednames = @()

# Procedure to write all the CSV values using procedural CSV construction
ForEach ($sc in $sclist)
{
	Try
	{
		Connect-NaController $sc -ErrorAction 'SilentlyContinue' | Out-Null
		Write-Host "Processing Controller $sc" -ForegroundColor Green
		if ($global:CurrentNaController -eq $null)
		{
			Write-Host "Could not connect to Controller $sc ! Skipping..." -ForegroundColor Red
			$failedcount++
			$failednames += $sc
			Continue
		}

	}
	Catch
	{
		Write-Host "Could not connect to Controller $sc ! Skipping..." -ForegroundColor Red
		$failedcount++
		$failednames += $sc
		Continue
	}

	#Generate another column header = storage controller name
	$CachedCsv[0] += $sc + ";"

	# run the stored command
	$getvalues = Invoke-Expression $optcmd

	# Clear the IGNORED array
	$ignored.clear()

	# For each value got...
	for ($i = 0; $i -lt $getvalues.count; $i++)
	{
		# Search this value's name in the input options array
		for ($j = 0; $j -lt $optIN.count; $j++)
		{
			# And if it matches exactly, write it there
			if ($getvalues[$i].name -contains $optIN[$j])
			{
				$presentIndex = [array]::IndexOf($optIn, $optIN[$j])
				$CachedCSV[$presentindex + 1] += $getvalues[$i].value + ";"
			}
			# If the value we are looking for does not exist, write an empty field and enter that value in the $ignored array
			# So it does not get checked over and over again.
			ElseIf (($getvalues.name -notcontains $optIN[$j]) -and ($ignored -notcontains $optIN[$j]))
			{
				$ignored += $optIN[$j]
				$missingIndex = [array]::IndexOf($optIn, $optIN[$j])
				$CachedCSV[$missingindex+1] += ";"
			}
		}
	}
}

# Write out the cached CSV
"Sep=;" | Out-File $csvpath1
$CachedCsv | Out-File $csvpath1 -Append

# Count statistics...
$endedtime = Get-Date
$timetook = ($endedtime - $startedtime).TotalSeconds

$scOK = $sclist.count - $failedcount

# Final stat listing.
Write-Host `n Processed $OptIN.count options on $scOK Storage Controllers 'in' $timetook seconds. -ForegroundColor Green
If ($failedcount -ge 1)
{
	Write-Host `n "Connection to the following $failedcount controllers failed: " $failednames `n -ForegroundColor 'Red'
}

And of course, since there is an option Retriever for NetApp, there must also be an option setter, right? Yes you are correct and that will be a subject of a future Scripting Corner 🙂 See you soon!

Scripting Corner: Send Commands as a root via SSH to ESXi with permit root login disabled.

Once upon a time when I wanted to remove many datastores from many hosts in the process of their decomission after consolidation, I was struck by an issue that can be found at VMware’s KB Article 2011220. The file used by SIOC was locked and because of that the datastore couldn’t be removed. To resolve this issue I would have to visit some 20 hosts via PuTTy and restart the StorageRM service. This seemed as a pretty tedious thing to do one-by-one, so I took the scripting approach 🙂

The only magic of this script lies in piping the root’s password in the plaintext (never visible to the user, but I guess this is not the maximum-security-best-practice) to the su -c command, which is will query you for the root password and then do a single command after successful authentication. You will need the plink utility to be able to send the commands via SSH.

For this script to work you will need to have an AD authentication to your ESXi host as well as the root password. What it basically does is retrieving a list of ESXi hosts from a text file, turning on SSH and disabling Lockdown Mode, runs the plink utility and then running a one-liner command with root privileges found inside the $runssh variable. Don’t worry about the last few lines being blue (aka commented) starting from line 71 – the crazy quotation is there to enable the root command’s execution. I let my own command /etc/init.d/storageRM start there so you can see the whole syntax.

function ESXSvc ($command, $service, $inESX)
{
 #Function starts here
 # Check if the service is running...
 $running = Get-VMHostService -VMHost $inESX | Where { $_.Key -eq "$service" } | Select -ExpandProperty Running

 If ($command -eq "start")
 {
 If ($running -eq $false)
 {
 Write-Host "Starting $service Service on $inESX"
 Get-VMHostService -VMHost $inESX | Where { $_.Key -eq "$service" } | Start-VMHostService -Confirm:$false | Out-Null
 }
 ELSE
 {
 Write-Host "The $service Service is already running on $inESX"
 }
 }
 ElseIf ($command -eq "stop")
 {
 If ($running -eq $true)
 {
 Write-Host "Stopping $service Service on $inESX"
 Get-VMHostService -VMHost $inESX | Where { $_.Key -eq “$service” } | Stop-VMHostService -Confirm:$false | Out-Null
 }
 Else
 {
 Write-Host "The $service Service is already stopped on $inESX"
 }
 }
} # Function ends here

$sshsvc = 'TSM-SSH'

Write-Host "--- ESXi Root Commands Probe ---`nUse to run as a root on a single or many ESXi hosts remotely `n" -ForegroundColor Green

# set variables for SCP transfer
$termapp = '.\plink.exe'
$esxilist = Get-Content esxi.txt

# Define username to be used for pushing the file via SCP, the user must be able to access the ESXi host with these credentials
$scpuser = Read-Host 'Enter your superuser account credentials in domain\user format'
$scppwd = Read-Host 'Enter your superuser password' -AsSecureString
$rootpw = Read-Host 'Enter ther Root Password for ESXi' -AsSecureString

ForEach ($esxi in $esxilist)
{
 Write-Host `n

 # Convert SecureStrings to plain password
 $BSTR0 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($scppwd)
 $PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR0)
 $BSTR1 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($rootpw)
 $PlainRootPw = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR1)

 Write-Host "********** Initiating connection to" $esxi "**********"`n -ForegroundColor 'Yellow'

 # Start SSH and Exit Lockdown mode...
 ESXSvc "start" $sshsvc $esxi
 write-host "Exitting Lockdown Mode"
 (Get-VMhost $esxi | Get-View).ExitLockdownMode() | Out-Null

 Write-Host "Storing RSA key in the host cache..." $esxi -ForegroundColor Green
 # If the host's key has not been cached before, send a "yes" before performing a blank function inside the session.
 $runssh = "cmd /c echo y | $termapp -pw $PlainPassword $scpuser`@$esxi exit"
 Invoke-Expression $runssh | Out-Null

 Write-Host "Running the command! `n" -ForegroundColor green
 # Enter your command you want to run as root in $shellcmd
 $shellcmd = "/etc/init.d/storageRM start"
 $cmd = "echo $plainrootpw | su -c `'$shellcmd`' "
 $runssh = "$termapp -pw $PlainPassword $scpuser`@$esxi `"$cmd`""
 Invoke-Expression $runssh 

 #Reset the converted password string to NULL
 $PlainPassword = $null
 $PlainRootPw = $null

 # Stop SSH and Enter Lockdown mode...
 ESXSvc "stop" $sshsvc $esxi
 Write-Host "Entering Lockdown Mode"
 (get-vmhost $esxi | get-view).EnterLockdownMode() | Out-Null

 Write-Host "********** Disconnected from" $esxi "**********" `n -ForegroundColor 'Yellow'

}

I hope this helped some of you to tackle an issue that needs root privileges and needs to be run at several hosts.

Scripting Corner: Command line arguments and Changing VM’s configuration parameters with PowerCLI

Sometimes it is useful to run a PowerShell script with commandline arguments in order to make the action faster – be it automation of the workflow or a number of batch jobs where foreach wouldn’t be usable.

The arguments you feed into your powershell scripts are stored in the global $args array, and we will operate with them as such.

Let’s assume we want to make a powershell script that will allow our 2nd level colleagues to be able to change the Virtual Machine’s Advanced Parameters without going to vCenter, having to shut down the VM, change values and then power it back on again – this would be done in a vCenter Orchestrator workflow that actually calls this script with parameters as inputs.

If you would do

.\test.ps1 a b c

The $args variable would be an array of @("a","b","c") – now you would like to pass these arguments further to your function either by putting them in its definition, or its code using the param(parameters). You can put a data type before the argument’s variable into brackets, such as string, int etc.

# Define the parameters while initializing the function and pass them to variables used inside
function main([string]$myparam = $args[0], [string]$yourparam = $args[1], [string]$anotherparam = $args[2]) {
Write-Host $myparam $yourparam $anotherparam
}

#Or you can do it like this, with param(parameters) inside the function itself:
function main2() {
#the param must be the very first line after the function definiton starts!
param([string]$myparam = $args[0], [string]$yourparam = $args[1], [string]$anotherparam = $args[2])
Write-Host $myparam $yourparam $anotherparam
}

Everytime you invoke either “main a b c” or “main2 a b c” you will have the output of “a b c” written in the console.

If this is a .ps1 file, you will just add the name of the function to the last line of your script. This will execute the function using the command-line arguments as its variables.

Pretty straightforward, right? You can find the whole script to change an advanced VM parameter below. No error checking with Try, Catch included yet (this will be a topic for next scripting corner), as this script expects an automated input.

# USAGE: .\script.ps1 vcenter virtualmachine advancedparametername advancedparametervalue
# EXAMPLE: .\script.ps1 myvcenter.org TESTVM01 disk.enableUUID false

# Define a function, get variables from the command line feed
function main([string]$vic = $args[0], [string]$vm = $args[1], [string]$parameter = $args[2], [string]$paramvalue = $args[3]) {

# Connect to vCenter
Write-Host "Connecting to vCenter" $vic
Connect-VIServer $vic | Out-Null

# Get VM...
Write-Host "Getting VM..." $vm
$target = Get-VM $vm

# ...and fetch its advanced settings...
Write-Host "Fetching advanced settings of the VM..."
$targetpara = $target | Get-AdvancedSetting | ? {$_.Name -like "$parameter"}

# Compare the new and current settings and proceed accordingly
If ($targetpara.value -ne $paramvalue) {
Write-Host "Applying newly configured settings..." `n "Name: " $parameter `n "Value: " $paramvalue

Set-AdvancedSetting -AdvancedSetting $targetpara -Value "$paramvalue" -Confirm:$false | Out-Null

Write-Host "Done!" -foregroundcolor Green
}
Else {
Write-Host "The target value has alredy been set!"
}

# Finally disconnect from the vCenter.
Disconnect-Viserver $vic | Out-Null
}

# initiate the function
main

See you around in the next Scripting Corner 😉