Archive

Archive for the ‘Toolbox’ Category

Last boot time with PowerShell

November 14th, 2012 No comments

Has to retrieve a bunch of boot times for a list of servers – quickly put together a function (I know there are many equally easy ways – but felt like retrieving info via PowerShell as part of a bigger report)

Function get-lastboot { 
 param($computername)
$date = Get-WmiObject Win32_OperatingSystem -ComputerName $computername | foreach{$_.LastBootUpTime}
$RebootTime = [System.DateTime]::ParseExact($date.split('.')[0],'yyyyMMddHHmmss',$null) 
$RebootTime

} #end function

Like so:

PS C:\scripts\Powershell> get-lastboot winhost7

20 October 2012 20:20:11
Categories: Powershell, Toolbox Tags:

Finding an unsused IP address on a subnet using Powershell

October 12th, 2012 No comments

I have been doing a bunch of P2Vs lately and sometimes when VMWare converter decides to not play along, I resort to offline conversions using Platespin convert.
Platespin likes to have about 4 IPs allocated for different reasons per conversion, so I decided to wroite a quick function to find unused IPs.

First of all, I alreday had some subnetting functions available (I found these online) – so all I had to do was write the extra bit of code.

Historically, I used to do this each time:

1..254 | %{$ip = "10.2.25.$_"; test-connection $ip -count 1 -ea 0}

This of course becomes a pain after a while, so this is where I went (the following all lives in my proifile, so is accesible each time I open a new Shell)

I have tried to track down the source of my original IP Subnetting function – I *think it was

function Get-UnusedIPAddress ($hostname, $subnet, $mask="255.255.255.0",$IPCount,[switch]$verbose,[switch]$all,$skip)
{
if ($verbose.IsPresent) {
  $VerbosePreference = 'Continue'
  Write-Verbose "Verbose Mode Enabled"
}
Else {
  $VerbosePreference = 'SilentlyContinue'
}

If ($hostname)
	{
	write-host "Attempting to access host NIC for accurate Subnet / Mask info" -fore green
	$hostdetails =  get-nics $hostname | ?{$_.DefaultIPGateway} |  select -first 1
	if ($hostdetails)
		{
		$subnet = $hostdetails | %{$_.IPAddress} | select -first 1
		$mask = $hostdetails | %{$_.IPsubnet} | select -first 1
		}
	else
		{
		write-host "No WMI access - will use NSLookup and simply scan the /24 address"
		}
	}
	if (!($subnet))
		{
		$subnet = [Net.Dns]::GetHostEntry("$hostname") | %{$_.Addresslist} | select -first 1 | %{$_.IPAddressToString}
		If (!($subnet)){return "Invalid request - please revise your query"}
		}
	$range = Get-NetworkSummary -IP $subnet -Mask $mask | %{$_.Range}
	write-host "Finding available IP(s) on $subnet/$mask - range $range" -fore green
	$range = Get-NetworkRange $subnet $mask
	$returnIPs = 1
	If ($skip){$range = $range | select -skip $skip}
	ForEach($rangeIP in $range)
		{
		If (!(test-connection $rangeIP -count 1 -ea 0))
			{
			write-host "Testing $rangeIP"
			If (($returnIPs -lt $IPcount) -OR ($all))
				{
				write-host $rangeIP -fore yellow
				$returnIPs++
				}
			Else
				{
				write-host $rangeIP -fore yellow
				return
				}
			}
		}
	return
}

The idea is that you can simply say

Get-UnusedIPAddress

The Function runs a WMI wuery against the specified hostname (or IP) and uses the IP and SM to find the first available IP after this.

Additional switches allow for the following:
$subnet – Specify the subnet to trawl (where you do not yet have a host in place) – Example : 10.2.1.0
$mask – If the function is unable to hit your specified host, it will defaul to a /24 Subnet mask – you may want to check a small / bigger range – Example 255.255.255.240
$IPCount – The Number of IPs that you would like returned
$all – Instructs the script to find all IPs in the range that are currently unused

I was surprised just how much I use this, once I had created it.

Supporting and useful functions that enable this script:


Function ConvertTo-BinaryIP {
  <#
    .Synopsis
      Converts a Decimal IP address into a binary format.
    .Description
      ConvertTo-BinaryIP uses System.Convert to switch between decimal and binary format. The output from this function is dotted binary.
    .Parameter IPAddress
      An IP Address to convert.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress
  )
 
  Process {
    Return [String]::Join('.', $( $IPAddress.GetAddressBytes() |
      ForEach-Object { [Convert]::ToString($_, 2).PadLeft(8, '0') } ))
  }
}

Function ConvertTo-DecimalIP {
  <#
    .Synopsis
      Converts a Decimal IP address into a 32-bit unsigned integer.
    .Description
      ConvertTo-DecimalIP takes a decimal IP, uses a shift-like operation on each octet and returns a single UInt32 value.
    .Parameter IPAddress
      An IP Address to convert.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress
  )
 
  Process {
    $i = 3; $DecimalIP = 0;
    $IPAddress.GetAddressBytes() | ForEach-Object { $DecimalIP += $_ * [Math]::Pow(256, $i); $i-- }
 
    Return [UInt32]$DecimalIP
  }
}

Function ConvertTo-DottedDecimalIP {
  <#
    .Synopsis
      Returns a dotted decimal IP address from either an unsigned 32-bit integer or a dotted binary string.
    .Description
      ConvertTo-DottedDecimalIP uses a regular expression match on the input string to convert to an IP address.
    .Parameter IPAddress
      A string representation of an IP address from either UInt32 or dotted binary.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [String]$IPAddress
  )
 
  Process {
    Switch -RegEx ($IPAddress) {
      "([01]{8}\.){3}[01]{8}" {
        Return [String]::Join('.', $( $IPAddress.Split('.') | ForEach-Object { [Convert]::ToUInt32($_, 2) } ))
      }
      "\d" {
        $IPAddress = [UInt32]$IPAddress
        $DottedIP = $( For ($i = 3; $i -gt -1; $i--) {
          $Remainder = $IPAddress % [Math]::Pow(256, $i)
          ($IPAddress - $Remainder) / [Math]::Pow(256, $i)
          $IPAddress = $Remainder
         } )
 
        Return [String]::Join('.', $DottedIP)
      }
      default {
        Write-Error "Cannot convert this format"
      }
    }
  }
}

Function ConvertTo-MaskLength {
  <#
    .Synopsis
      Returns the length of a subnet mask.
    .Description
      ConvertTo-MaskLength accepts any IPv4 address as input, however the output value
      only makes sense when using a subnet mask.
    .Parameter SubnetMask
      A subnet mask to convert into length
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Alias("Mask")]
    [Net.IPAddress]$SubnetMask
  )
 
  Process {
    $Bits = "$( $SubnetMask.GetAddressBytes() | ForEach-Object { [Convert]::ToString($_, 2) } )" -Replace '[\s0]'
 
    Return $Bits.Length
  }
}

Function ConvertTo-Mask {
  <#
    .Synopsis
      Returns a dotted decimal subnet mask from a mask length.
    .Description
      ConvertTo-Mask returns a subnet mask in dotted decimal format from an integer value ranging
      between 0 and 32. ConvertTo-Mask first creates a binary string from the length, converts
      that to an unsigned 32-bit integer then calls ConvertTo-DottedDecimalIP to complete the operation.
    .Parameter MaskLength
      The number of bits which must be masked.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Alias("Length")]
    [ValidateRange(0, 32)]
    $MaskLength
  )
 
  Process {
    Return ConvertTo-DottedDecimalIP ([Convert]::ToUInt32($(("1" * $MaskLength).PadRight(32, "0")), 2))
  }
}

Function Get-NetworkAddress {
  <#
    .Synopsis
      Takes an IP address and subnet mask then calculates the network address for the range.
    .Description
      Get-NetworkAddress returns the network address for a subnet by performing a bitwise AND
      operation against the decimal forms of the IP address and subnet mask. Get-NetworkAddress
      expects both the IP address and subnet mask in dotted decimal format.
    .Parameter IPAddress
      Any IP address within the network range.
    .Parameter SubnetMask
      The subnet mask for the network.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress,
 
    [Parameter(Mandatory = $True, Position = 1)]
    [Alias("Mask")]
    [Net.IPAddress]$SubnetMask
  )
 
  Process {
    Return ConvertTo-DottedDecimalIP ((ConvertTo-DecimalIP $IPAddress) -BAnd (ConvertTo-DecimalIP $SubnetMask))
  }
}


Function Get-BroadcastAddress {
  <#
    .Synopsis
      Takes an IP address and subnet mask then calculates the broadcast address for the range.
    .Description
      Get-BroadcastAddress returns the broadcast address for a subnet by performing a bitwise AND
      operation against the decimal forms of the IP address and inverted subnet mask.
      Get-BroadcastAddress expects both the IP address and subnet mask in dotted decimal format.
    .Parameter IPAddress
      Any IP address within the network range.
    .Parameter SubnetMask
      The subnet mask for the network.
  #>
 
  [CmdLetBinding()]
  Param(
    [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)]
    [Net.IPAddress]$IPAddress,
 
    [Parameter(Mandatory = $True, Position = 1)]
    [Alias("Mask")]
    [Net.IPAddress]$SubnetMask
  )
 
  Process {
    Return ConvertTo-DottedDecimalIP $((ConvertTo-DecimalIP $IPAddress) -BOr `
      ((-BNot (ConvertTo-DecimalIP $SubnetMask)) -BAnd [UInt32]::MaxValue))
  }
}


Function Get-NetworkSummary ( [String]$IP, [String]$Mask ) {
  If ($IP.Contains("/"))
  {
    $Temp = $IP.Split("/")
    $IP = $Temp[0]
    $Mask = $Temp[1]
  }
 
  If (!$Mask.Contains("."))
  {
    $Mask = ConvertTo-Mask $Mask
  }
 
  $DecimalIP = ConvertTo-DecimalIP $IP
  $DecimalMask = ConvertTo-DecimalIP $Mask
 
  $Network = $DecimalIP -BAnd $DecimalMask
  $Broadcast = $DecimalIP -BOr
    ((-BNot $DecimalMask) -BAnd [UInt32]::MaxValue)
  $NetworkAddress = ConvertTo-DottedDecimalIP $Network
  $RangeStart = ConvertTo-DottedDecimalIP ($Network + 1)
  $RangeEnd = ConvertTo-DottedDecimalIP ($Broadcast - 1)
  $BroadcastAddress = ConvertTo-DottedDecimalIP $Broadcast
  $MaskLength = ConvertTo-MaskLength $Mask
 
  $BinaryIP = ConvertTo-BinaryIP $IP; $Private = $False
  Switch -RegEx ($BinaryIP)
  {
    "^1111"  { $Class = "E"; $SubnetBitMap = "1111" }
    "^1110"  { $Class = "D"; $SubnetBitMap = "1110" }
    "^110"   {
      $Class = "C"
      If ($BinaryIP -Match "^11000000.10101000") { $Private = $True } }
    "^10"    {
      $Class = "B"
      If ($BinaryIP -Match "^10101100.0001") { $Private = $True } }
    "^0"     {
      $Class = "A"
      If ($BinaryIP -Match "^00001010") { $Private = $True } }
   }  
 
  $NetInfo = New-Object Object
  Add-Member NoteProperty "Network" -Input $NetInfo -Value $NetworkAddress
  Add-Member NoteProperty "Broadcast" -Input $NetInfo -Value $BroadcastAddress
  Add-Member NoteProperty "Range" -Input $NetInfo `
    -Value "$RangeStart - $RangeEnd"
  Add-Member NoteProperty "Mask" -Input $NetInfo -Value $Mask
  Add-Member NoteProperty "MaskLength" -Input $NetInfo -Value $MaskLength
  Add-Member NoteProperty "Hosts" -Input $NetInfo `
    -Value $($Broadcast - $Network - 1)
  Add-Member NoteProperty "Class" -Input $NetInfo -Value $Class
  Add-Member NoteProperty "IsPrivate" -Input $NetInfo -Value $Private
 
  Return $NetInfo
}

Function Get-NetworkRange( [String]$IP, [String]$Mask ) {
  If ($IP.Contains("/"))
  {
    $Temp = $IP.Split("/")
    $IP = $Temp[0]
    $Mask = $Temp[1]
  }
 
  If (!$Mask.Contains("."))
  {
    $Mask = ConvertTo-Mask $Mask
  }
 
  $DecimalIP = ConvertTo-DecimalIP $IP
  $DecimalMask = ConvertTo-DecimalIP $Mask
 
  $Network = $DecimalIP -BAnd $DecimalMask
  $Broadcast = $DecimalIP -BOr ((-BNot $DecimalMask) -BAnd [UInt32]::MaxValue)
 
  For ($i = $($Network + 1); $i -lt $Broadcast; $i++) {
    ConvertTo-DottedDecimalIP $i
  }
}
Categories: Powershell, Toolbox Tags:

Useful scripts for amending the number of ports per vSwitch

May 1st, 2012 No comments

We have found that on many (most) of our ESX hosts, we run at risk of running our of vbSwitch ports when we run through our maintenance periods and reduce the number of hosts in a cluster.
Below are the scripts that I use to amend these settings:

# Calculating ports in use on each vSwitch: 
$myCol = @() 
ForEach ($VMHost in (get-cluster "prod Core" | 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 

“To upgrade the port numbers on vSwitch1 on myesxhost.intra to 120 ports , you need:”

# To increase number of ports on a vSwitch 
Get-VMHost "myserver1.intra" | % {Get-VirtualSwitch -VMHost $_ -Name vSwitch0 | where {$_.NumPorts -lt "128"}} | % { Set-VirtualSwitch -VirtualSwitch $_ -NumPorts "128" } 

“Note, the PowerCli says ‘128’ – the reason for this is that VMware reserves 8 ports for overhead on each vswitch.
(http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1008040)

You’ll also want to vMotion the Vms off each host – you can highlight multiple VMs and drag and drop, or right click, select ‘migrate’ and follow the wizard.
If you want to PowerCli it, you can try the following:”

# For a controlled migration. moving all VMs on myserver1 to myserver2 
get-vmhost myserver1.intra | get-vm | move-vm -destination (get-vmhost myserver2.intra) 

“The above will prompt you on each VM – you can ignore all prompts with :”

# to Ignore prompts 
get-vmhost myserver1.intra | get-vm | move-vm -destination (get-vmhost myserver2.intra) -Confirm:$false 

We previously had a bug that NICs lost the connection state(i.e. – the ‘Connected’ box got unchecked) – the solution was to move the VMs to a host that had available switch ports and then set the Nic’s to connected.”

#To find disconnected NICs in Prod Core cluster:" 
$vms = Get-Cluster "Prod Core" | Get-VM 
Get-NetworkAdapter ($vms | where {$_.powerstate -eq “poweredon”}) | Where { $_.connectionstate.connected -eq “$F” } | select parent, connectionstate 
#To repair change all disconnected NICs in a cluster to connected 
$vms = Get-NetworkAdapter ((Get-Cluster "Prod Core HA" | Get-VM) | where {$_.powerstate -eq “poweredon”}) | Where { $_.connectionstate.connected -eq “$F” } | select parent, connectionstate 
foreach ($a in $vms) 
{ 
$vm = get-vm $a.parent 
write-host $a.parent -back green 
if (!(test-connection $vm.name )) 
   { 
   $nics = $vm | get-networkadapter | Where {$_.ConnectionState.Connected -eq $false -and $_.ConnectionState.StartConnected -eq $true} 
   if ($nics -ne $null) 
   { 
           foreach ( $nic in $nics ) 
           { 
                     write-host $vm.Name 
                     write-host $nic 
                        If (test-connection  $vm.Guest.HostName -count 1 -ea 0){write-host "Returning Pings!!"; return}else{write-host "Not Returning Pings"} 
         } 
                $nic | Set-NetworkAdapter -Connected $true 
        } 
  
                        If (test-connection  $vm.Guest.HostName -count 3 -ea 0){write-host "Returning Pings!!"}else{write-host "Not Returning Pings"} 
        } 
} 
Categories: Powershell, Toolbox, VMWare Tags:

Authenticating to your proxy when your application does not have the ability – option 2

November 16th, 2011 No comments

Previously,

I dropped a post on here on using a squid proxy tp get around the issue where your network needs Proxy authentication, but you application does not support it. 

http://www.get-virtual.info/2011/01/20/authenticating-through-a-proxy-when-the-app-has-no-option-to-do-so/

I was actually at the time working on gathering some info during web requests – using Fiddler – and figured that as Fiddler was proxying my requests, it may be able to manage my authentication for me and save some pain.

well here is a quick way to do this.

  1. head over to http://www.fiddler2.com/fiddler2/ and install the latest version of fiddler
  2. close IE etc – so no web requests are running
  3. Launch fiddler (using the standard configuration)
  4.  Start IE and make an HTTP-request to an external web-site.
  5. At this point, the proxy authorization dialogue pops up
  6. Fill in your credentials
  7. Go back to fiddler and run a search for “Proxy-Authorization”.
  8. click on any of the (recent) found lines in the left hand pane, then click “headers” from the buttons on the right
  9. Look through the text fpor a header value like “Basic  ArBiTaRy64BaSeEnCode==”
  10. Copy the string to your clipboard
  11. To edit your rules go Rules > Customize Rules (Or hit Ctrl-R)
  12. In the section : “OnBeforeRequest” add the following (using the values you captured above):

// Add proxy auth header
    oSession.oRequest[“Proxy-Authorization”] = “Basic ArBiTaRy64BaSeEnCode==”;

13. Restart Fiddler – Enjoy

 

Categories: Toolbox Tags:

ADSI from WinPE

September 21st, 2011 No comments

Johan Arwidmark  over at http://www.deployvista.com/Home/tabid/36/EntryID/127/language/sv-SE/Default.aspx has an ADSI plugin for Windows PE, (the download link below has x86 and x64 support.)

 

He has recently replaced his site with http://www.deploymentresearch.com – but I am unable to find the link on this site (hence the link to the old site below)

This is very useful for those who want to hit the AD from deployment HTAs etc when creating server builds and so on

He has even created very simple batch files for creating the updated boot wims (though I assume of course that you all have relatively custom wims as is, so may want to pick through the batch file and substitute your own boot wim as a source)

Download link:
http://www.deployvista.com/Repository/tabid/71/EntryId/61/DMXModule/396/Download/attachment/language/en-US/Default.aspx

 

Categories: SCCM / SMS, Toolbox Tags:

Using Vyatta as firewall in ESX/ESXi for Private network simulation, routing, firewalls, DHCP and identifying port requirements

February 18th, 2011 No comments

VMware is an amazing tool for emulating physical Firewalls, Routers, DHCP servers. It is especially useful for helping identify port requirements of various applications and tools.
Quite often, as a consultant, people ask you to implement some new product and expect you to provide all port requirements for the product, so that relevant firewall rules etc can be created – but they won’t allow you to drop anything like Wireshark on their live network.

My normal solution is to create my own ‘Private’ network on an ESX host (which could be your mobile lab)
This allows me to isolate traffic behind an ‘appliance’ firewall / router and if I like, drop a VM on that private network, to do port capture etc.

Of course, this is also a great tool for simulating routing / firewalls in your home lab, providing DHCP and so on.

In the following example, I’ll use Vyatta to build a Private network and then do some port monitoring.
Read more…

Categories: Toolbox, VMWare Tags: ,

Script of the Day – quick and easy VMware Powershell scripts

February 16th, 2011 No comments

Today’s script of the day is more a collection of scripts (or rather an easy way of generating a bunch of scripts)

Over at the VMware labs (http://labs.vmware.com/) they have released an awesome tool (in Alpha at the moment) that interecepts instructions sent to your Virtual Center and in the background generates PowerCli (or javascript or C# or Soap) code for you. Read more…

Script of the Day – Creating AD groups without QAD cmdlets

February 10th, 2011 No comments

We’re automating some server builds and need to create AD groups to manage resource access to each Server (company policy)
We use SCCM for deployment and I wanted to automate the groups (at build time)
Also, I did not want to have any dependencies on external Modules (so I want to do this without the quest tools)

I found a few documents online for creating AD groups that went without the quest tools, but found that most did not work.

You see, our key problem was that we wwanted to create Domain Local Security Groups.
In VBScript, this was pretty simple, as on the ‘put’ portion of group creation, you just told the script to apply both Group type constants, using an or statement.
The theory was that this would work in Powershell too (and many online script seemed to indicate that it would) – but the group types being created were inconsistent.

Below are the constants for the different group types:

Value GroupType
2 Global distribution group
4 Domain local distribution group
8 Universal distribution group
-2147483646 Global security group
-2147483644 Domain local security group
-2147483640 Universal security group

So creating a Domain Local Security group is as simple as:

$groupType = -2147483644
$objOU = [ADSI]&quot;LDAP://localhost:389/OU=YourOUName,DC=Example,DC=com&quot;
$GroupName = &quot;MyNewGroup&quot;
$objGroup = $objOU.Create(&quot;group&quot;, &quot;CN=&quot; + $GroupName)
$objGroup.Put(&quot;groupType&quot;, $groupType )
$objGroup.Put(&quot;sAMAccountName&quot;, $GroupName )
$objGroup.SetInfo()

Of course you can change the LDAP binding to a DC (rather than localhost) and you can change the GroupType by amending the value of $GroupType
Even better, you could wrap it into a Function, with a switch statement to take care of Group type selection.

Happy days.

Quicker copies with Robocopy . .

February 7th, 2011 No comments

So I was messing about with Robocopy over the weekend and I decided to re-run an old copy job, using a new Desktop.
Strangely, my copy did not kick off like it did before. The problem – the new version of robocopy has new features and new syntax (yes I know it has been out a while – thanks clever clogs . . . )

anyway, long story short, looking at the updated switches, I noticed :

/MT[:n] :: Do multi-threaded copies with n threads (default 8).
n must be at least 1 and not greater than 128.
This option is incompatible with the /IPG and /EFSRAW opt

How awesome is that! The new robocopy runs Multi threaded and can be ramped up to 128 Threads!!!!!!! (this is Vista onwards I believe. – seems something good actually did come with Vista?)

Categories: Powershell, Toolbox Tags: , ,

Script of the Day – shutdown your VMware ESX estate with PowerCLI

February 7th, 2011 No comments

The following script is straight from
http://www.virtu-al.net/2010/01/06/powercli-shutdown-your-virtual-infrastructure/

I have used it a few times and it is very effective and easy to use.


Connect-VIServer MyVIServer

# Get All the ESX Hosts
$ESXSRV = Get-VMHost

# For each of the VMs on the ESX hosts
Foreach ($VM in ($ESXSRV | Get-VM)){
# Shutdown the guest cleanly
$VM | Shutdown-VMGuest -Confirm:$false
}

# Set the amount of time to wait before assuming the remaining powered on guests are stuck
$waittime = 200 #Seconds

$Time = (Get-Date).TimeofDay
do {
# Wait for the VMs to be Shutdown cleanly
sleep 1.0
$timeleft = $waittime - ($Newtime.seconds)
$numvms = ($ESXSRV | Get-VM | Where { $_.PowerState -eq "poweredOn"}).Count
Write "Waiting for shutdown of $numvms VMs or until $timeleft seconds"
$Newtime = (Get-Date).TimeofDay - $Time
} until ((@($ESXSRV | Get-VM | Where { $_.PowerState -eq "poweredOn" }).Count) -eq 0 -or ($Newtime).Seconds -ge $waittime)

# Shutdown the ESX Hosts
$ESXSRV | Foreach {Get-View $_.ID} | Foreach {$_.ShutdownHost_Task($TRUE)}

Write-Host ";Shutdown Complete"

BE WARNED – IF YOU TEST THIS ON YOUR LIVE ENVIRONEMT, YOU’RE GOING TO GET IN TROUBLE!!!!