30
Oct/09
3

Report vSphere Alarms with Powershell

Wow, vSphere vCenter Server has a lot of new alarms! Great for monitoring your environment. But a pain when it comes to documenting it. Thank God VMware for the PowerCLI! Just a few lines of code can do the documentation for you.

Here’s how:

# Report vSphere Alarms
# by Hugo Peeters
# www.peetersonline.nl

#Region Variables
$VCServer = "myVCServer"
$Outfile = "D:\scripts\Alarms.txt"

Connect-VIServer $VCServer
$SI = Get-View ServiceInstance
$AM = Get-View $SI.Content.AlarmManager
$myCol = @()
ForEach ($Folder in (Get-Folder))
	{
	ForEach ($Alarm in ($AM.GetAlarm(($Folder|Get-View).MoRef)))
		{
		$Alarm = Get-View $Alarm
		$myObj = "" | Select Folder, Name, Description, Enabled, Summary
		$myObj.Folder = $Folder.Name
		$myObj.Name = $Alarm.Info.Name
		$myObj.Description = $Alarm.Info.Description
		$myObj.Enabled = $Alarm.Info.Enabled
		$myObj.Summary = $Alarm.Info.Expression.Expression | Out-String
		$myObj
		$myCol += $myObj
		}
	}
$myCol | Where {$_.Enabled} | Out-File $Outfile
ii $Outfile
  • Share/Bookmark
15
Sep/09
10

Add Vmx Path to VI Client using Powershell

The following script is a request from David Gontie, who was kind enough to comment on a previous post.

He’d like to add the location of his vm’s to a custom field. This is especially handy if you store all the files for a vm in a single datastore.

Here you go David:

##############################
# Script created by Hugo Peeters #
# http://www.peetersonline.nl   #
##############################
 
# Variables
$VCServerName = "MYVCSERVER"
$CustomFieldName = "VMX"
$ManagedObjectType = "VirtualMachine"
 
# Script
$VC = Connect-VIServer $VCServerName
$SI = Get-View ServiceInstance
$CFM = Get-View $SI.Content.CustomFieldsManager
 
$myCustomField = $CFM.Field | Where {$_.Name -eq $CustomFieldName}
If (!$myCustomField)
	{
	# Create Custom Field
	$FieldCopy = $CFM.Field[0]
	$CFM.AddCustomFieldDef($CustomFieldName,$ManagedObjectType,$FieldCopy.FieldDefPrivileges,$FieldCopy.FieldInstancePrivileges)
	$myCustomField = $CFM.Field | Where {$_.Name -eq $CustomFieldName}
	}
 
# Fill Custom Fields
$VMs = Get-VM
ForEach ($VM in $VMs)
	{
	$VMView = $VM | Get-View
	$VMXPath = $VMView.Config.Files.VMPathName
	# Compare value to current value
	If ($VMXPath -ne ($VMView.CustomValue | ?{$_.Key -eq $myCustomField.Key}).Value)
		{
		# Set Custom Value
		$VMView.setCustomValue($CustomFieldName,$VMXPath)
		}
	Clear-Variable VMXPath -ErrorAction SilentlyContinue
	Clear-Variable VMView -ErrorAction SilentlyContinue
	}
Disconnect-VIServer -Confirm:$False

Enjoy!
Hugo

  • Share/Bookmark
27
Aug/09
3

Set VMware Snapshot Location with Powershell

Snapshots are m*th3rfcukers. If you’re not careful, they will mass-murder your vms. Yet they allow you to time-travel! You want to use them, but how to prevent a massacre? Here’s how: relocate the delta files.

When you create a snapshot, the current state of the vm is preserved by leaving the disk files alone. All changes since the moment of creating the snapshot are written to delta files. The delta files are stored in the vm’s working directory. The working directory is – by default – the location where the vmx and other config files reside. If that datastore runs out of free space – especially if it also contains disk files – you’re in a bit of a kerfuffle. Vms not booting or being frozen as if they stared into Medusa’s reptoid eyes.

So you can do two things: reserve overhead in your datastores and stay afraid some overactive snapshot might destroy your environment, or set the working directory of your vms to some big-ass datastore you don’t use for anything else and let the snapshots enjoy themselves. If they fill up the datastore, they only kill all vms with snapshots, not the rest.

But how, you ask? You can edit the vmx files of you vms manually - which requires your vms to be powered down – and add the line: workingDir = “/vmfs/volumes/<insanely long guid you need to somehow find>/”

Or, you run my script and change the working location on the fly:

# Description:
# Set's the Working Location for a VM. This location will hold the vswap and snapshot delta files.

# Usage:
# First, connect to a virtual server server (Connect-VIServer)
#
# Set snapshot location for one vm (folder must already exist on datastore):
# Set-VMSnapshotLocation -vm VM001 -Location "[BigAssDatastore] Snapshots"
#
# For all vms at once (no folder specified will put all in the root of the datastore):
# Get-VM | ForEach-Object {Set-VMSnapshotLocation -vm $_ -Location "[BigAssDatastore]"}
#
# Reset the location to the original location (with the vmx file):
# Set-VMSnapshotLocation -vm VM001 -Reset

Function Set-VMSnapshotLocation
	{
	param($vm, [string]$Location, [switch]$Reset=$False)

	#Region Input Validation
	# Check vm parameter
	If (!$vm)
		{
		Throw "No VM specified."
		}
	# Check Location parameter.
	# Can be empty when using Reset, can be "[datastore]" or can be "[datastore] folder" (folder must exist).
	If (!$Reset -and $Location -notmatch "\[\S+\](\s\w+)?")
		{
		Throw "Invalid location specified. Syntax: [Datastore] Folder"
		}
	# Specifying Reset will set Location to empty, which causes the original location to be used (same location as vmx file).
	If ($Reset)
		{
		$Location = ""
		}
	# Make sure to get a vm object to work with.
	# This allows both using a vm name when targeting one vm (Set-VMSnapshotLocation -vm VM001 -Reset);
	# AND piping a collection of vm's (Get-VM | ForEach (Set-VMSnapshotLocation -vm $_ -Location "[BigAssDatastore] Snapshots" ).
	If ($vm.GetType().Name -eq "String")
		{
		$oVM = Get-VM $vm
		}
	ElseIf ($vm.GetType().Name -eq "VirtualMachineImpl")
		{
		$oVM = $vm
		}
	Else
		{
		Throw "Parameter vm is of an invalid type."
		}
	#EndRegion InputValidation

	#Region Main
	# Get advanced properties of vm
	$vmView = $oVM | Get-View
	# Create object containing new properties
	$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
	$vmConfigSpec.Files = New-Object VMware.Vim.VirtualMachineFileInfo
	$vmConfigSpec.Files.SnapshotDirectory = $Location
	# Reconfigure VM using new properties
	$vmView.ReconfigVM($vmConfigSpec)
	#EndRegion Main
	}

You can download it here: Set-VMSnapshotLocation (rename to .ps1)

I tried hard as I could, but I could not get my vm to die. Even running the script, creating a snapshot, running the script to move the working location again and deleting the snapshot did not prove a problem. However, I urge you to be careful with it. I’m not responsable for anything you do with it.

Tiny glitch: the VI Client shows the wrong datastore location when a vm has a snapshot in a modified workingdir.

Enjoy.

  • Share/Bookmark
15
Jul/09
0

User Confirmation in Powershell

Built-in cmdlets usually offer the -Confirm parameter whenever you need user confirmation. When writing your own scripts, you might want to ask the eventual user of the script for confirmation yourself. This handy little function (store it in your profile!) allows you to ask for confirmation whenever, where-ever. (Try not to bug your users too much.)

# Description: Ask a user to answer a question with either yes or no.
 
# Example use: 
# If (Ask-YesOrNo)
# 	{ 
#	# User has confirmed
#	...
#	}
#	Else
#	{
#	# User did not confirm
#	...
#	}
 
Function Ask-YesOrNo
	{
	param([string]$title="Confirm",[string]$message="Are you sure?")
	$choiceYes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Answer Yes."
	$choiceNo = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Answer No."
	$options = [System.Management.Automation.Host.ChoiceDescription[]]($choiceYes, $choiceNo)
	$result = $host.ui.PromptForChoice($title, $message, $options, 1)
		switch ($result)
    	{
			0 
			{
			Return $true
			}
 
			1 
			{
			Return $false
			}
		}
	}
  • Share/Bookmark
24
Jun/09
0

Create a vSwitch Port Overview with Powershell

WARNING: VMware vmotion does not check wether there are sufficient ports available on the virtual switch on the destination host. Migrating a vm to a host with insufficient ports will cause the vmotion to complete without warnings, yet the virtual NIC will be disconnected! This issue is descripbed in this KB article.
The solution to this problem is to create vSwitches with sufficient ports, obviously. Do you want to know how many ports are currently being used on every vSwitch in your environment? vSphere PowerCLI to the rescue! Try the following script:

$VC = Connect-VIServer "MyVCServer"
$myCol = @()
ForEach ($VMHost in (Get-VMHost | Sort Name))
	{
	ForEach ($VM in ($VMHost | Get-VM))
		{
		ForEach ($NIC in (Get-NetworkAdapter -VM $VM))
			{
			$myObj = "" | Select VMHost, VM, NIC, PortGroup, vSwitch
			$myObj.VMHost = $VMHost.Name
			$myObj.VM = $VM.Name
			$myObj.NIC = $NIC.Name
			$myObj.PortGroup = Get-VirtualPortGroup -VM $VM -Name $NIC.NetworkName
			$myObj.vSwitch = $myObj.PortGroup.VirtualSwitchName
			$myCol += $myObj
			}
		}
	}
$myCol | Group-Object VMHost, vSwitch -NoElement | Sort Name | Select Name, Count
Disconnect-VIServer -Confirm:$false
  • Share/Bookmark
12
Jun/09
1

Examine VMware CPU Ready Times with Powershell

When your (VMware) consolidation ratios are becoming high, it might be smart to keep an eye on your vm’s CPU Ready Times. Unfortunately, by default, the VI Client will only show realtime ready time statistics. Plus you’d have to look at each vm individually. Thank God VMware for the PowerCLI! Read this document for more information on how to interpret the results.

# Variables
$OutFile = "D:\Scripts\ReadyTimes.csv"
$VIServer = "MyVIServer.domain.local"
 
# Connect to Virtual Center
$VI = Connect-VIServer $VIServer
 
$myCol = @()
ForEach ($VMHost in (Get-VMHost | Sort Name))
	{
	ForEach ($VM in ($VMHost | Get-VM | Sort Name))
		{
		# Gather Stats
		$Ready = $VM | Get-Stat -Stat Cpu.Ready.Summation -RealTime
		$Used = $VM | Get-Stat -Stat Cpu.Used.Summation -RealTime
		$Wait = $VM | Get-Stat -Stat Cpu.Wait.Summation -RealTime
		For ($a = 0; $a -lt $VM.NumCpu; $a++)
			{
			$myObj = "" | Select VMHost, VM, Instance, %RDY, %USED, %WAIT
			$myObj.VMHost = $VMHost.Name
			$myObj.VM = $VM.Name
			$myObj.Instance = $a
			$myObj."%RDY" = [Math]::Round((($Ready | Where {$_.Instance -eq $a} | Measure-Object -Property Value -Average).Average)/200,1)
			$myObj."%USED" = [Math]::Round((($Used | Where {$_.Instance -eq $a} | Measure-Object -Property Value -Average).Average)/200,1)
			$myObj."%WAIT" = [Math]::Round((($Wait | Where {$_.Instance -eq $a} | Measure-Object -Property Value -Average).Average)/200,1)
			$myCol += $myObj
			}
		Clear-Variable Ready -ErrorAction SilentlyContinue 
		Clear-Variable Wait -ErrorAction SilentlyContinue 
		Clear-Variable Used -ErrorAction SilentlyContinue 
		Clear-Variable myObj -ErrorAction SilentlyContinue
		}
	}
 
Disconnect-VIServer -Confirm:$false
 
# Export and launch output
$myCol | Export-Csv $OutFile
Invoke-Item $OutFile
  • Share/Bookmark
4
Jun/09
3

Managing Scheduled Tasks Remotely Using Powershell

The following Powershell functions allow you to manage querying, creating and removing scheduled tasks on one or more computers remotely.
The functions use schtasks.exe, which is included in Windows. Unlike the Win32_ScheduledJob WMI class, the schtasks.exe commandline tool will show manually created tasks, as well as script-created ones. The examples show some, but not all parameters in action. I think the parameter names are descriptive enough to figure it out, really. If not, take a look at schtasks.exe /?. One tip: try piping a list of computer names to foreach-object and into this function.
Enjoy.
Hugo

Function Get-ScheduledTask
	{
	param([string]$ComputerName = "localhost")
	Write-Host "Computer: $ComputerName"
	$Command = "schtasks.exe /query /s $ComputerName"
	Invoke-Expression $Command
	Clear-Variable Command -ErrorAction SilentlyContinue
	Write-Host "`n"
	}
 
# EXAMPLE: Get-ScheduledTask -ComputerName Server01
 
Function Remove-ScheduledTask
	{
	param(
	[string]$ComputerName = "localhost",
	[string]$TaskName = "blank"
	)
	If ((Get-ScheduledTask -ComputerName $ComputerName) -match $TaskName)
		{
		If ((Read-Host "Are you sure you want to remove task $TaskName from $ComputerName(y/n)") -eq "y")
			{
			$Command = "schtasks.exe /delete /s $ComputerName /tn $TaskName /F"
			Invoke-Expression $Command
			Clear-Variable Command -ErrorAction SilentlyContinue
			}
		}
	Else
		{
		Write-Warning "Task $TaskName not found on $ComputerName"
		}
	}
 
# EXAMPLE: Remove-ScheduledTask -ComputerName Server01 -TaskName MyTask
 
Function Create-ScheduledTask
	{
	param(
	[string]$ComputerName = "localhost",
	[string]$RunAsUser = "System",
	[string]$TaskName = "MyTask",
	[string]$TaskRun = '"C:\Program Files\Scripts\Script.vbs"',
	[string]$Schedule = "Monthly",
	[string]$Modifier = "second",
	[string]$Days = "SUN",
	[string]$Months = '"MAR,JUN,SEP,DEC"',
	[string]$StartTime = "13:00",
	[string]$EndTime = "17:00",
	[string]$Interval = "60"	
	)
	Write-Host "Computer: $ComputerName"
	$Command = "schtasks.exe /create /s $ComputerName /ru $RunAsUser /tn $TaskName /tr $TaskRun /sc $Schedule /mo $Modifier /d $Days /m $Months /st $StartTime /et $EndTime /ri $Interval /F"
	Invoke-Expression $Command
	Clear-Variable Command -ErrorAction SilentlyContinue
	Write-Host "`n"
	}
 
# EXAMPLE: Create-ScheduledTask -ComputerName MyServer -TaskName MyTask02 -TaskRun "D:\scripts\script2.vbs"
  • Share/Bookmark
2
Jun/09
0

Increment PowerGUI Xml Version with Powershell

If you are using PowerGUI (which you should) and some of your collaegues do too, you might want to use a central configuration. Whenever you want to update the central configuration xml file, you need to increment the version number in order to push this change out to your collaegues. The following function increments the version number for you and even allows you to store the new file to the central location at the same time. All without having to edit the complex xml file manually. It even saves a backup copy of your central config file in case you mess up ;) It assumes you use a simple x.y version number, so please start out with 2.0 when setting up your config.
To update your central config: just make the modifications within PowerGUI and then run this function.

Function Increment-PowerGuiXmlVersion
	{
	Param(
	$PersonalXmlFile = (Join-Path $env:appdata "Quest Software\PowerGUI\config.xml"),
	$CentralXmlFile = 'P:\Centrally Managed Files\config.xml',
	$XPath = "//container[@name='version']",
	$IncrementMajor = $false,
	$IncrementMinor = $true
	)
 
	# Load central xml file (for backup and reading the current version number)
	$CentralConfig = New-Object System.Xml.XmlDocument
	$CentralConfig.Load($CentralXmlFile)
 
	# Select the correct node
	$NodeToRead = $CentralConfig.SelectSingleNode($XPath)
 
	# Read the current version
	$CurrentVersion = $NodeToRead.Value
	$MajorVersion = [Int]($CurrentVersion.Split(".")[0])
	$MinorVersion = [Int]($CurrentVersion.Split(".")[1])
	Write-Host "Current Version of Central Xml File: $CurrentVersion"
 
	# Backup central xml file
	$BackupFileName = $CentralXmlFile.Replace(".xml", ("_{0}.xml" -f $CurrentVersion))
	$CentralConfig.Save($BackupFileName)
 
	# Load personal xml file
	$PersonalConfig = New-Object System.Xml.XmlDocument
	$PersonalConfig.Load($PersonalXmlFile)
 
	# Select the correct node
	$NodeToBeChanged = $PersonalConfig.SelectSingleNode($XPath)
 
	# Increment the version
	If ($IncrementMinor){$MinorVersion++}
	If ($IncrementMajor){$MajorVersion++; $MinorVersion = 0}
	$newVersion = [string]::Concat($MajorVersion,".",$MinorVersion)
	Write-Host "New Version of Central Xml File: $newVersion"
 
	# Write the new version
	$NodeToBeChanged.Value = $newVersion
 
	# Save the personal xml file as the new central xml file
	$PersonalConfig.Save($CentralXmlFile)	
	}
  • Share/Bookmark
26
May/09
3

Get SQL database size using Windows Powershell

The following script examines servers from (part of) your Active Directory domain, identifies SQL servers and lists the instances with their total database sizes.

# Get-SQLSizes.ps1
# Created by Hugo Peeters
# http://www.peetersonline.nl
# Description: Finds SQL Servers in Active Directory and gets the total database size in GB.
 
#Region VARIABLES
	$SearchRoot = "OU=Servers,DC=domain,DC=local"
	$OutFile = 'D:\scripts\SQLSizes.csv'
	$Digits = 2
#EndRegion VARIABLES
 
#Region SCRIPT
	[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.Smo') | Out-Null
	$myCol = @()
	ForEach ($Server in (Get-QADComputer -SearchRoot $SearchRoot -SizeLimit 0 | Sort Name))
		{
		ForEach ($Instance in (Get-WmiObject -Class Win32_Service -ComputerName $Server.Name | Where {$_.Name -like 'MSSQL$*'}))
			{
			If ($Instance -eq $null){break}
			# Connect to SQL
			$InstanceString = "{0}\{1}" -f $Server.Name, $Instance.Name.Split('$')[1]
			$Sql = New-Object ('Microsoft.SqlServer.Management.Smo.Server') $InstanceString
			# Gather information
			$myObj = "" | Select Instance, TotalDBSizeGB
			$myObj.Instance = $InstanceString
			$myObj.TotalDBSizeGB = [Math]::Round((($Sql.Databases | ForEach {$_.Size} | Measure-Object -Sum).Sum * 1MB / 1GB),$Digits)
			$myCol += $myObj
			# Cleanup
			Clear-Variable Sql -ErrorAction SilentlyContinue
			Clear-Variable InstanceString -ErrorAction SilentlyContinue
			}
		}	
#EndRegion SCRIPT
 
#Region OUTPUT
	$myCol | Export-Csv $OutFile -NoTypeInformation
	Invoke-Item $OutFile
#EndRegion OUTPUT
  • Share/Bookmark
26
May/09
4

Oneliner: Service Console IP with PowerCLI

Getting the Service Console IP addresses of your ESX servers with vSphere PowerCLI (formerly known as the VI Toolkit for Powershell):

Get-VMHost | Select Name, @{N="ConsoleIP";E={(Get-VMHostNetwork).ConsoleNic | ForEach{$_.IP}}}
  • Share/Bookmark