Nigel Boulton's Blog
9Nov/130

VMworld PowerCLI Group Discussion

Whilst at VMworld in Barcelona last month, my old friend Alan Renouf asked me to help him out with one of his PowerCLI and Automation sessions. For those who don't know him, Alan is the Automation Frameworks Product Manager at VMware, a total PowerCLI guru and a co-author of the definitive PowerCLI book 'VMware vSphere PowerCLI Reference: Automating vSphere Administration'.

The session was a lively group discussion and the audience comprised of a great mix of people ranging from PowerCLI beginners right through to experts. It was my job as Alan's 'beautiful assistant' [don't know about that!] to capture the useful information flying around the room on a flip chart. With impressive use of his deciphering skills, Alan has written up the result on his personal blog in a series of four excellent posts. There is some good stuff in there for PowerCLI scripters at all stages in the learning process. You can find these posts via the links below – thanks Al!

VMworld PowerCLI Group Discussion–Part 1–Getting Started

VMworld PowerCLI Group Discussion–Part 2–Resources

VMworld PowerCLI Group Discussion–Part 3–Launching and Using

VMworld PowerCLI Group Discussion–Part 4–Advanced tools and scripting

Post to Twitter

Filed under: PowerCLI, VMware No Comments
6Feb/131

Using JH Software’s Simple DNS Client Library for .NET in PowerShell

Last week I had a requirement to perform a DNS lookup within a PowerShell script, using a DNS server other than the one that the machine in question was configured to use. This turned out to be quite a challenge and, having found a library that looked like it would do it, I ended up having to call on the expertise of my good friend and PowerShell MVP Jonathan Medd to help me out!

As I'm sure I can't be the first person that has attempted this in PowerShell using the library in question (or something similar) I felt it was worth blogging the methodology…

I won't bore you with too much of the background, but I was amending a script that I wrote a few years back which used the .NET class System.Net.Dns to perform a DNS lookup. This worked fine until my ISP changed the behaviour of their DNS servers to return a search page in the event that the host record in question was not found. Although this is probably helpful to a human being, I wanted my script to raise an error in the event that the host record wasn't found - and this was no longer happening because my ISP's DNS now returns the IP address of the server delivering the search page instead. Therefore, to my script, the DNS lookup was appearing to be successful!

The obvious answer to this is to use a DNS server that doesn't behave in this way. Google's DNS is an example of this. I didn't want to change my entire infrastructure to use Google's DNS servers, so the most sensible approach was to have just the script use them. This is where the next problem became apparent – System.Net.DNS does not allow the use of alternative DNS servers.

After some Googling, I came across the excellent and free 'SimpleDNS DNS Client Library for .NET' from JH Software here. This allows you to specify DNS servers to perform lookups against. However (perhaps because I'm not a .NET programmer) I couldn't work out for the life of me how to set the required property, and that is where Jonathan helped me out. The methodology is described below. I can't take any credit for this – it's all his work Smile

The first step is obviously to download the SimpleDNS library from here, and put at least the Dynamic Link Library 'JHSoftware.DnsClient.dll' into a suitable location on the local machine, say 'C:\PowerShell\Libraries'. (There is a .CHM help file included in the download that is worth including too.)

Here is the entire code snippet used. An explanation of each line follows in the text below.

[System.Reflection.Assembly]::LoadFile("C:\PowerShell\Libraries\JHSoftware.DnsClient.dll")            

$ServerIPObj = [System.Net.IPAddress]::Parse("8.8.8.8"),[System.Net.IPAddress]::Parse("8.8.4.4")            

$OptionsObj = New-Object JHSoftware.DnsClient+RequestOptions
$OptionsObj.DnsServers = $ServerIPObj            

$IPVersion = [JHSoftware.DnsClient+IPVersion]::IPv4            

$HostAddress = [JHSoftware.DnsClient]::LookupHost("www.microsoft.com",$IPVersion,$OptionsObj)
$HostAddress[0].ToString()

First, the line:

[System.Reflection.Assembly]::LoadFile("C:\PowerShell\Libraries\JHSoftware.DnsClient.dll")

loads the library from the previously given location.

The next step is to create an array of .NET IPAddress objects which are set to Google's DNS server (8.8.8.8 and 8.8.4.4). This is done using the 'Parse' static method which converts a string into an IP address:

$ServerIPObj = [System.Net.IPAddress]::Parse("8.8.8.8"),[System.Net.IPAddress]::Parse("8.8.4.4")

Next, we need to create a new .NET object to hold the request options, and set the 'DnsServers' property of that. For VB.NET and C# examples (but no PowerShell Sad smile), see the programming guide here.

$OptionsObj = New-Object JHSoftware.DnsClient+RequestOptions
$OptionsObj.DnsServers = $ServerIPObj

The 'LookupHost' method requires an additional parameter because we are using 'RequestOptions' – an enumeration of the IP protocol version to be used, which is called DnsClient.IPVersion. The base class is System.Enum. To create this in PowerShell with the correct value you need use the method below, specifying the protocol version with the appropriate member name. There is a great post from Lincoln Atkinson on using enumerations in PowerShell here.

$IPVersion = [JHSoftware.DnsClient+IPVersion]::IPv4

Once this has been done, we have all the parameters we need to call the LookupHost method and return a result:

$HostAddress = [JHSoftware.DnsClient]::LookupHost("www.microsoft.com",$IPVersion,$OptionsObj)

Now let's take a look at $HostAddress:

Address           : 456734529
AddressFamily     : InterNetwork
ScopeId           :
IsIPv6Multicast   : False
IsIPv6LinkLocal   : False
IsIPv6SiteLocal   : False
IPAddressToString : 65.55.57.27

I wanted just the IP address as a string in a variable for the script to process further, and how to do this eluded me initially until I realised that the $HostAddress object was in fact being returned by the library as a System.Net.IPAddress object, and effectively as an array. The MSDN documentation for a similar method of the System.Net.Dns class, the GetHostAddresses method, helped me to work this out. So returning the IP address alone is simply a matter of:

$HostAddress[0].ToString()

…and that's it!

Jonathan and I decided that we would both blog this information as it could potentially be useful to a number of people. He has taken this a stage further and turned the code above into a very cool function, making it easy to drop into a script for future re-use (I will be incorporating this into my script!). You can see his blog post on this here.

Post to Twitter

30May/125

Configuring Syslog for all your vSphere Hosts using PowerCLI

If you have a need to configure remote Syslog logging for all (or perhaps a subset) of your vSphere hosts at the same time, PowerCLI can help!

Let's assume that your remote Syslog server has the IP address 192.168.1.10. Using the following PowerCLI one-liner, you can configure all hosts managed by a particular vCenter to send Syslog data to this server:

Get-VMHost | Set-VMHostSysLogServer -SysLogServer 192.168.1.10 -SysLogServerPort 514


To configure only the hosts in a particular cluster to do this, you would use:

Get-Cluster 'Cluster Name' | Get-VMHost | Set-VMHostSysLogServer -SysLogServer 192.168.1.10 -SysLogServerPort 514


The above assumes that you are using the vSphere PowerCLI console, and have already connected to the appropriate vCenter server as so:

Connect-VIServer -Server vcenter.domain.com

Easy! Automation really is great…

Post to Twitter

31Jan/124

Tracking down registry changes made by Group Policy Objects

I was troubleshooting an issue a week or so ago in conjunction with a Citrix Technical Support Engineer, and thought I would blog a handy tip that my esteemed colleague Phil Reeve pointed out to me, which assisted in tracking down the problem.

So, let me set the scene for you. We had discovered a problem where one of our Citrix Web Interfaces was no longer notifying users of the impending requirement to change their password, during the period prior to the date on which they would be forced to do so by Active Directory.

Citrix Technical Support had been working on this problem on our behalf for a few weeks, and had been able to reproduce it successfully. I won't go into great detail here, as this is a post about troubleshooting Group Policy and not Citrix problems – suffice to say that Citrix had narrowed it down to an incorrectly set registry value on a Citrix Zone Data Collector (ZDC) that the Web Interface was reading and processing.

The value in question was HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\passwordexpirywarning. On the ZDC, it had been incorrectly set to 0. The next challenge was to find out what had done this in order to be able to change it back to the correct value, and make it stay that way.

The "passwordexpirywarning" value can be set by local or Active Directory Group Policy via the setting "Interactive Logon: Prompt user to change password before expiration". A quick check in the Local Group Policy Editor (gpedit.msc) on the affected ZDC indicated it was being set by Active Directory Group Policy, as it was greyed out:

PromptUserSetByGPO

So how to track down which Group Policy Object (GPO) was applying this incorrect value? "Try GPResult.exe or the Group Policy Results Wizard in the GPMC" I hear you shout! Of course, I tried these tools, but to no avail, as they have a "limitation" (shall we call it?) in that they don't reliably show all the registry values that Group Policy processes – and this was one such case. I have seen the same thing many a time over the years I have been working with Active Directory, and to be honest, am quite surprised that this is still the case after all this time.

So finally, to the point of this post. Below is the tip I mentioned, in the form of a procedure that can be used should you find yourself in a similar position (paths and GUIDs have been changed to protect the innocent!).

1. Open a Windows Explorer window on your domain's Sysvol folder. This will be something like "\\mydomain.com\Sysvol\mydomain.com"

2. In the Explorer Search field in this window, type the name of the registry value you are working with (in my case "passwordexpirywarning")

SearchResultsNotFound

3. Press Enter and wait for the search to complete – it won't find anything, so you will then need to click Search in File Contents. Once the search completes, you should have one or more GptTmpl.inf files returned in the results, as shown below:

SearchResults

4. Open each of these files in turn in Notepad (or your preferred text editor), and search each for the registry value in question:

SearchInFile

In the above screenshot, you can see that this is a "candidate" GPO as it is setting "passwordexpirywarning" to 0. Ignore any files that don't correspond to the value you are interested in

5. For each of the corresponding GptTmpl.inf files that contain the value in question, obtain the GPO GUID (Globally Unique Identifier) by inspecting the properties of the GptTmpl.inf file, as shown below, and copying and pasting the GUID part of the path, EXCLUDING the curly brackets {} into a temporary text file:

GetGPOGUID

The aim of this is to build up a list of GUIDs for candidate GPOs

6. Now, if you are not already on a Windows 2008 R2 server with PowerShell and the Group Policy Management Console (GPMC) installed, log on to one, and open a PowerShell session

7. In the PowerShell session, type the following command:

Import-Module GroupPolicy

followed by

Get-GPO -GUID <GUID>

where <GUID> is the first GUID you collected in the text file above, for example

Get-GPO -GUID AD77FD0E-3E55-4B7B-AD7A-2C6B4E680F80

This should return the display name of the GPO corresponding to this GUID, as shown below:

PS C:\> Import-Module GroupPolicy
PS C:\> Get-GPO -GUID AD77FD0E-3E55-4B7B-AD7A-2C6B4E680F80

DisplayName      : Terminal Server Security Policy
DomainName       : mydomain.com
Owner            :
Id               : ad77fd0e-3e55-4b7b-ad7a-2c6b4e680f80
GpoStatus        : AllSettingsEnabled
Description      :
CreationTime     : 08/01/2011 11:17:44
ModificationTime : 27/01/2012 10:48:26
UserVersion      : AD Version: 1, SysVol Version: 1
ComputerVersion  : AD Version: 25, SysVol Version: 25
WmiFilter        :

PS C:\>

8. Copy the display name of the GPO into a text file and repeat the Get-GPO command for each of the other GUIDs you collected in the previous text file, collecting each GPO's name in the new text file as you go

All that remains is to determine which of these GPOs are applied to the affected machine:

9. Log on to the affected machine as an administrator and run gpresult /v > gpresult.txt in a command prompt. This will create a text file with a detailed output from GPResult.exe

10. Open the gpresult.txt file in Notepad (or your preferred text editor), and search for the name of each GPO collected from the Get-GPO cmdlet. Once located, this will confirm which GPO(s) are applied to the affected machine

Armed with the information determined by this procedure, I was able to update another GPO that was applied only to the ZDC in question and set the "passwordexpirywarning" value to the correct number of days, and password notifications then worked as expected.

Post to Twitter

19Oct/110

Virtual Machine search in vSphere Client does not return expected results

I was recently responsible for troubleshooting a problem where searching for certain virtual machines in the vSphere Client didn't always return the expected results. The problem was occurring in both our production and non-production environments and the symptom was as described below:

In the vSphere Client, in Hosts and Clusters view, selecting (for example) a Datacenter in the tree in the left pane, then typing the name of an existing VM into the "Name, State or Guest OS contains:" box on the Virtual Machines tab wouldn't always return the VM in the search results. In some cases the VM search would behave in this way when targeted at at the Datacenter level, in others at the vCenter level and in others still, at the cluster level. The "Search Inventory" box exhibited the same behaviour. It was, however, possible to target the search at the host on which the VM resided and have it returned consistently in the search results. Similar behaviour occurred when searching for VMs in VMs and Templates view, and in all cases the VM in question continued to be displayed in the tree in the left pane of the vSphere Client.

After some searching online, I decided to raise a call with VMware Support. The Engineer who called me back immediately knew the cause of the problem, and directed me to a VMware Knowledge Base article:

Sort sequence is incorrect and sorting/scrolling in the Virtual Machines tab in vCenter Server is slow (1029665)

Neither of these observations were the case in our environments, but the underlying cause was the same – it was, as the article says, "due to a conservative Java Memory Pool setting on which the Tomcat service depends for various functions. This issue usually occurs when the number of virtual machines is more than 500, but is dependent on a number of factors in your environment." In our environments at the time we had 850 and 300 VMs respectively.

If you have a 64-bit vCenter (4.x) Server, increasing the value of the memory pool in the Java Memory Pool settings is an easy fix:

  1. On the vCenter Server, click Start > All Programs > VMware > VMware Tomcat > Configure Tomcat
  2. Click the Java tab
  3. Double the number in the Initial and Maximum memory pool field (defaults are 256 and 1024 MB respectively)
  4. Click OK
  5. Verify that there are no tasks running in the environment
  6. Restart the VirtualCenter Server service – this will also restart the VirtualCenter Management Webservices service, as the latter is dependent. Bear in mind that anybody running the vSphere Client will be logged off when the services restart

If you have a 32-bit vCenter Server, follow the instructions under "Additional Information" in the article.

Post to Twitter

10Sep/1111

Wake-on-LAN using a PowerShell Script

I currently have a home lab based on Simon Gallagher's excellent vTARDIS. This is an HP ProLiant ML115 which runs 8 virtual ESXi hosts arranged into a cluster which in turn run 30 Linux virtual machines. As you can imagine, this takes quite a while to start up! For this reason, I wanted a way to start the physical host programmatically (either on a scheduled basis or remotely via my home automation interface), in preparation for working on it or doing some learning. So a PowerShell script was obviously the answer! Within the lab itself I have a PowerCLI script which automatically powers up the various components in the correct order (but that's another story...).

The script I developed is shown below. I decided to share it via my blog as a) it might be useful to someone and b) it demonstrates the use of some handy PowerShell 2 features, such as advanced functions, parameter sets, parameter validation and comment-based Help, along with other good stuff like regular expressions, error handling and writing to the Windows event log. In the distant past I used an ActiveX control called UltraWOL (from UltraJones software, who don't seem to be around anymore) and called it from VBScript scripts to facilitate Wake-on-LAN, but I wanted to find a nice 'PowerShell only' way of implementing it.

The script can be used in two ways: it can be provided with a CSV file which maps 'known' machines to their MAC addresses, and then used to wake up one of these machines by providing just the computer name as a parameter, or alternatively, simply by specifying a MAC address using the 'MACAddress' parameter, to have a Wake-on-LAN magic packet containing that MAC address sent. In either case, results are reported to standard output and also recorded in the Application event log.

The script is shown below, but for your convenience you can download a copy here (MD5 checksum: 0533313E71C42816217F948247C17F9E) to save you copying and pasting (and all the potential problems associated with doing that) should you want to make use of it. Below the listing is a brief outline of the operation of the script.

<#
.NOTES
	================================================================================
	Filename:	Wake-Machine.ps1

	Author:		Nigel Boulton, http://www.nigelboulton.co.uk

	Version:	1.00

	Date:		10 Sep 2011

	Mod dates:	

	Notes:		See http://www.nigelboulton.co.uk/2011/09/
							wake-on-lan-using-a-powershell-script/
				for further details of this script
	================================================================================
.SYNOPSIS
	Wakes up a machine using Wake-on-LAN
.DESCRIPTION
	If the ComputerName parameter is given and is the name of a known machine (specified
	in the CSV file 'MACLookup.csv', stored in the same folder as this script), sends a
	Wake-on-LAN magic packet containing the MAC address of that machine. The ComputerName
	parameter is the default, so if omitted the script will expect any argument given to
	be the name of a known machine

	The format for the CSV file is as follows. Header information as shown is required in
	this file:

	ComputerName,MACAddress
	pc1,00-16-da-2b-6f-b8

	If the alternative MACAddress parameter is given and is a valid MAC address, sends a
	Wake-on-LAN magic packet containing that MAC address. In this case the MACLookup.csv
	file is not used and is not required to be present

	Results are reported to standard output and also recorded in the Application event log
.EXAMPLE
    Wake-Machine.ps1 -ComputerName PC1

	Description
	-----------
	Sends a Wake-on-LAN magic packet for the known machine 'PC1'
.EXAMPLE
    Wake-Machine.ps1 PC1

	Description
	-----------
	Sends a Wake-on-LAN magic packet for the known machine 'PC1'
.EXAMPLE
	Wake-Machine.ps1 -MACAddress 00-16-DA-2B-6F-B8

	Description
	-----------
	Sends a Wake-on-LAN magic packet containing the MAC address 00-16-DA-2B-6F-B8
.LINK

http://www.nigelboulton.co.uk/2011/09/wake-on-lan-using-a-powershell-script/

#>
[CmdletBinding(DefaultParameterSetName='ComputerName')]
param(
 [Parameter(Mandatory=$true,
      HelpMessage="Enter a known machine name. See Help for this script for further information",
      Position=0,
      ParameterSetName='ComputerName')]
    [ValidateNotNullOrEmpty()]
 [string]$ComputerName,            

 [Parameter(Position=0,
      ParameterSetName='MACAddress')]
 [ValidatePattern('^([0-9a-fA-F]{2}[:-]{0,1}){5}[0-9a-fA-F]{2}$')]
 [string]$MACAddress
)            

function Send-MagicPacket {
 param(
 [Parameter(Mandatory=$true,
      HelpMessage="Enter a valid MAC address")]
 [ValidatePattern('^([0-9a-fA-F]{2}[:-]{0,1}){5}[0-9a-fA-F]{2}$')]
 [string]$MAC
 )
 <#
	.NOTES
		================================================================================
		Purpose: 		To send a Wake-on-LAN magic packet with a specified MAC address
		Assumptions:
		Effects:
		Inputs:
		 $MAC:			MAC address to include in packet
		Calls:
		Returns:		

		Notes:			Based on http://thepowershellguy.com/blogs/posh/archive/
										2007/04/01/powershell-wake-on-lan-script.aspx
		================================================================================
	.SYNOPSIS
		Sends a Wake-on-LAN magic packet containing a specified MAC address
	.DESCRIPTION
	    Sends a Wake-on-LAN magic packet containing a specified MAC address. The MAC
		address is specified by the MAC parameter. The octets of the MAC address may
		be separated by dashes '-', colons ':' or nothing
	.EXAMPLE
		Send-MagicPacket -MAC 00-16-DA-2B-6F-B8

		Description
		-----------
		Sends a Wake-on-LAN magic packet containing the MAC address 00-16-DA-2B-6F-B8
	.EXAMPLE
		Send-MagicPacket -MAC 00:16:DA:2B:6F:B8

		Description
		-----------
		Sends a Wake-on-LAN magic packet containing the MAC address 00-16-DA-2B-6F-B8
	.EXAMPLE
		Send-MagicPacket -MAC 0016DA2B6FB8

		Description
		-----------
		Sends a Wake-on-LAN magic packet containing the MAC address 00-16-DA-2B-6F-B8
	.LINK

http://www.nigelboulton.co.uk/2011/09/wake-on-lan-using-a-powershell-script/

	#>            

 # Use regex to strip out separators (: or -) if present and split string every second character
 # Piping to Where-Object {$_} avoids empty elements between each pair of characters
 $MACArray = ($MAC -replace '[-:]', [String]::Empty) -split '(.{2})' | Where-Object {$_}            

 $MACByteArray = $MACArray | ForEach-Object {[Byte]('0x' + $_)}
 $UDPClient = New-Object System.Net.Sockets.UdpClient
 $UDPClient.Connect(([System.Net.IPAddress]::Broadcast),4000)
 $Packet = [Byte[]](,0xFF * 6)
 $Packet += $MACByteArray * 16
 Write-Debug "Magic packet contents: $([bitconverter]::ToString($Packet))"
 [void]$UDPClient.Send($Packet, $Packet.Length)
 Write-Debug "Wake-on-LAN magic packet of $($Packet.Length) bytes sent to $($MAC.ToUpper())"
}            

#***********************************************************************************
# Start of script
#Requires -Version 2
Set-StrictMode -Version 2            

# User configurable values
$MACLookupFilePath = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) MACLookup.csv # Location and name of MAC address lookup file            

# Initialise variables
$ComputerDisplayName = $Null ; $Msg = $Null            

# Register event log source if required (requires admin rights)
if (!(Test-Path HKLM:\SYSTEM\CurrentControlSet\Services\Eventlog\Application\$($MyInvocation.MyCommand.Name))) {
 Try {
  New-EventLog -LogName Application -Source $MyInvocation.MyCommand.Name -ErrorAction Stop
 }
 Catch {
  Write-Host "WARNING: Unable to register event log source. You must run this script at least once as an administrator to do this" -ForegroundColor Red -BackgroundColor Black
 }
}            

if ($PsCmdlet.ParameterSetName -eq 'ComputerName') { # Computer name specified            

 # Import CSV file of MAC addresses for known machines into a hash table.
 # This makes it easy to check whether a given machine exists in this list,
 # and retrieve its MAC address if so
 $MACLookup = @{}
 Import-CSV -Path $MACLookupFilePath | ForEach-Object {$MACLookup[$_.ComputerName] = $_.MACAddress}            

 # Find MAC address for machine in hash table (this is case insensitive)
 if ($MACLookup.ContainsKey($ComputerName) -eq $True) {
  $MACAddress = $MACLookup.Get_Item($ComputerName)
  $ComputerDisplayName = "($($ComputerName.ToUpper()))"
  # Validate MAC address
  if ($MACAddress -notmatch '^([0-9a-fA-F]{2}[:-]{0,1}){5}[0-9a-fA-F]{2}$') {
   $Msg = "ERROR: Invalid MAC address specified: '$MACAddress'. Verify the MAC address for the computer '$ComputerName' in the CSV file '$MACLookupFilePath'"
   Write-Host $Msg -ForegroundColor Red -BackgroundColor Black
   Try {
    Write-EventLog -LogName Application -Source $MyInvocation.MyCommand.Name -EventID 1001 -EntryType Error -Message $Msg -Category 0
   }
   Catch {
    Write-Host "WARNING: Unable to write to event log. You must run this script at least once as an administrator to register the event log source" -ForegroundColor Red -BackgroundColor Black
   }
   Throw "Invalid MAC address"
  }
 } else {
  $Msg = "ERROR: Unrecognised computer name: '$($ComputerName.ToUpper())'. Add the computer name and MAC address to the CSV file '$MACLookupFilePath'"
  Write-Host $Msg -ForegroundColor Red -BackgroundColor Black
  Try {
   Write-EventLog -LogName Application -Source $MyInvocation.MyCommand.Name -EventID 1001 -EntryType Error -Message $Msg -Category 0
  }
  Catch {
   Write-Host "WARNING: Unable to write to event log. You must run this script at least once as an administrator to register the event log source" -ForegroundColor Red -BackgroundColor Black
  }
  Throw "Unrecognised computer name"
 }
}            

Send-MagicPacket -MAC $MACAddress
$Msg = ("Wake-on-LAN magic packet sent to $($MACAddress.ToUpper()) $ComputerDisplayName").Trim()
Write-Output $Msg
Try {
 Write-EventLog -LogName Application -Source $MyInvocation.MyCommand.Name -EventID 1000 -EntryType Information -Message $Msg -Category 0
}
Catch {
 Write-Host "WARNING: Unable to write to event log. You must run this script at least once as an administrator to register the event log source" -ForegroundColor Red -BackgroundColor Black
}

The first section of the script is the comment-based Help which can be displayed by typing Get-Help .\Wake-Machine.ps1 in a PowerShell console in the usual way (I'd advise that you do this to help understand the operation of the script before attempting to implement it).

Following that are the definitions for the two possible parameters. Note their parameter set names - these are called 'ComputerName' and 'MACAddress' respectively. You will notice that the default parameter set has been set as 'ComputerName'. This means that if the parameter name is omitted, the script will expect any argument given to be the name of a known machine in the CSV file.

The CSV file must be called 'MACLookup.csv' and stored in the same folder as this script. The format for the CSV file is as follows - header information as shown below is required in the file. A sample CSV file is included with the downloadable script:

ComputerName,MACAddress
pc1,00-16-da-2b-6f-b8

If the 'MACAddress' parameter is given, a regular expression is then used with parameter validation (ValidatePattern) to verify that it is a valid MAC address. The octets of the MAC address may be separated by dashes '-', colons ':' or nothing.

Coming on to the main body of the script you will notice that I use

Set-StrictMode -Version 2

This is a good practice which ensures that you don't get caught out by mis-typed variable names. I use this in all my scripts. For more information see this TechNet document.

The next significant operation is registering an event log source for the script in the Windows Application event log, if that hasn't already been done. This requires you to be an administrator, so it will be necessary to run the script once as an administrator if you will normally run it as a non-admin. If you don't do this, the script will continue without writing the result to the event log.

The next section is only executed if the 'ComputerName' parameter set is in operation: the MACLookup.csv file is read into a hash table, then this is queried to see whether the computer name specified by the parameter exists, and the corresponding MAC address is retrieved if so, and then validated by the same regular expression as before. If not, the script writes this fact to the event log and throws an error.

Finally, the 'Send-MagicPacket' function is called to send the Wake-on-LAN magic packet and the result is reported to standard output and the event log.

The Send-MagicPacket function is of course at the heart of the script functionality. It is based on a great post by The PowerShell Guy that you can find here (thanks /\/\o\/\/). I tweaked it slightly because I thought it would be more flexible to be able to specify the octets of the MAC address separated by either dashes, colons or nothing. I also added some comment-based Help so that you can easily copy and paste this function into other scripts or a PowerShell module, to make use of it elsewhere. You will notice that you can use the PowerShell common parameter 'Debug' to troubleshoot creation and sending of the magic packet if necessary. See Jeffery Hicks (The Lonely Administrator)'s excellent post here for more information on how and why you might do that.

I hope this script is of use to you (or perhaps the techniques within it)  – please leave a comment to let me know if so.

Post to Twitter

30Jun/111

Black Console and 100% CPU after restoring a Windows 2003 Virtual Machine

I was recently involved in a Disaster Recovery rehearsal. The idea behind this was to prove that we could recover our key systems at another site should a disaster occur. We came across an interesting issue which I thought I would blog about in case it is of help to anyone who may also encounter it in a similar situation. Let's face it, in a disaster recovery scenario, you need as few difficult issues to deal with as possible..!

This issue is only really likely to affect virtual servers (provided you are using identical hardware to recover your physical ones that is).

We were using IBM Tivoli Storage Manager (TSM) to restore C: drive and System State backups of virtual servers (taken in the live environment) into a separate isolated network.

The process involved creating a new VM (typically from a template), with the same virtual hardware version, number of vCPUs, amount of memory, disk layout and virtual NIC type as the live server. This VM would have the same operating system version, edition, architecture (x86/amd64) and service pack installed as the live server, plus the TSM client to facilitate the restore.

On restoring the first Windows 2003 server in this manner, the server wouldn't boot. The VM console displayed a black screen (no error message) and the VM CPU usage immediately spiked to 100% and stayed there. This happened immediately after power on, so it was not possible to get the VM to respond to the F8 key in an attempt to put it into safe mode, to assist with troubleshooting.

It really looked like a hardware incompatibility, but I'd been very careful to make sure the necessary parameters matched, so was a bit mystified. After some head scratching and time spent comparing the hardware that Windows thought was present in the template and live VM (good job it wasn't a real disaster!), I spotted that the Hardware Abstraction Layer (HAL) didn't match between the two (the HAL can be checked via Device Manager – Computer). The template VM had an ACPI Uniprocessor HAL (which I expected as it had been built with only one vCPU), but the live VM had an ACPI Multiprocessor HAL. I was pretty sure at this point that this would be the cause of the issue.

Like the template VM, the live VM also had one vCPU, so why did it have a multiprocessor HAL? The key difference between the two VMs was how they had been created. The live VM had originally been created by P2V'ing a physical server. This physical server would no doubt have had multiple processors and hence when Windows was originally installed, had been given a multiprocessor HAL. This didn't change on P2V, but the person who did this elected for the VM to only have one vCPU - quite understandably as it was a relatively lightly loaded server. So it was running a single vCPU with a multiprocessor HAL (which is clearly a valid configuration).

The problem was introduced by the restore process. I assume that some aspect of the restore didn't replace something in the template, and part of the template VM's uniprocessor HAL was still operational after the restore and reboot – or not operational in fact!

The supported/correct way of setting a multiprocessor HAL would be to install the OS from scratch on a VM with more than one vCPU. However, that would have been time consuming for the number of variations of servers that we had to restore, and the time available didn't allow for that.

So how did I rectify this? Well, a few years ago I ran into a (different) issue attempting to give a singe vCPU VM an additional processor, and in the process of troubleshooting that, came across this post on ngohq.com by Squall Leonhart. This describes how to change the Windows HAL without reinstalling. Note that this approach is, obviously, totally unsupported!

The method involved using DevCon, which is basically a command-line version of Device Manager. DevCon can be downloaded from Microsoft here.

By running the following commands within the template VM, prior to the restore, I was able to update the HAL to an ACPI Multiprocessor one:

devcon sethwid @ROOT\ACPI_HAL\0000 := +acpiapic_mp !acpiapic_up
devcon update c:\windows\inf\hal.inf acpiapic_mp

Squall recommends rebooting twice after doing this, to ensure that the device and IRQ tables get updated correctly.

After performing the steps above, a subsequent TSM restore was successful, and the server booted with no further problems. Result!

I have reproduced Squall's entire post below as this is such useful information which could be lost should the ngohq.com forum cease to exist for any reason – which would be a massive shame. It includes information on how to go to and from various HALs. Thanks for this incredibly helpful information Squall!

Heres some tips for upgraders!

You require the Devcon utility for this, unpack it to a folder, then navigate to the folder its in using Command prompt (command prompt on context menu PowerToy is handy for this)

How to enable APIC without repair installing windows
in device manager you will notice that under computer type it says Advanced Power and Control Interface PC.. this is a standard single processor HAL driver without APIC. to upgrade to the APIC driver you input the following:

devcon sethwid @ROOT\ACPI_HAL\0000 := +acpiapic_up !acpipic_up
devcon update c:\windows\inf\hal.inf acpiapic_up

after this, enable APIC in the bios if you haven't already, and reboot twice so windows can update the device and irq tables, it should now say ACPI Uniprocessor PC in the device manager

How to go back to PIC
if you wish to go back to PIC from APIC enter this:

devcon sethwid @ROOT\ACPI_HAL\0000 := +
acpipic_up !acpiapic_up
devcon update c:\windows\inf\hal.inf acpipic_up

and reboot twice to update the device and IRQ tables, and then disable APIC in the bios (the reason is, if you disable APIC before the device and irq tables update, windows will crash at startup.

How to Update from a Single Core APIC compatible cpu to a Multicore APIC compatible cpu

under the computer entry in the device manager, you will see it says ACPI Uniprocessor PC, to update to the multiprocessor HAL input this:

devcon sethwid @ROOT\ACPI_HAL\0000 := +acpiapic_mp !acpiapic_up
devcon update c:\windows\inf\hal.inf acpiapic_mp.

Then reboot twice again to update the device and IRQ tables.

How to go back to Single Core (should it be needed)
if you accidentally burn your processor and have to go back to a single core backup, you input this into the devcon:

devcon sethwid @ROOT\ACPI_HAL\0000 := +acpiapic_up !acpiapic_mp
devcon update c:\windows\inf\hal.inf acpiapic_up.

and always reboot twice.
__________________

Post to Twitter

Filed under: VMware, Windows 1 Comment
7May/1112

Problems with PowerShell Comment-based Help

I was writing a script recently and came across a couple of "gotchas" when attempting to use PowerShell 2's Comment-based Help at script level. A simple test script to demonstrate what I am about to describe is shown below:

<#
.SYNOPSIS
    Tests whether PowerShell 2 Comment-based Help is working as expected
.DESCRIPTION
    Displays Comment-based Help for this script
.EXAMPLE
    Test-CommentBasedHelp.ps1
.NOTES
    None
#>
Write-Host "Hello World"


The expected output from this is as follows:

Expected output from test script showing Comment-based Help working

"Get-Help about_comment_based_help" says

"SYNTAX FOR COMMENT-BASED HELP IN SCRIPTS

  Comment-based Help for a script can appear in one of the following two

  locations in the script.

  -- At the beginning of the script file. Script Help can be preceded in the 
     script only by comments and blank lines.

  -- If the first item in the script body (after the Help) is a function 
     declaration, there must be at least two blank lines between the end of the 
     script Help and the function declaration. Otherwise, the Help is 
     interpreted as being Help for the function, not Help for the script.

  -- At the end of the script file."

Looks straightforward enough. However, if you try the following:

# Comment
<#
.SYNOPSIS
    Tests whether PowerShell 2 Comment-based Help is working as expected
.DESCRIPTION
    Displays Comment-based Help for this script
.EXAMPLE
    Test-CommentBasedHelp.ps1
.NOTES
    None
#>
Write-Host "Hello World"

The output isn't as expected. You just get the name of the script returned, as shown below:

Output from test script showing just script name returned

What's that about…? Well, this is the first "gotcha". Although Get-Help says that Script Help can be preceded in the script by comments and blank lines, it's easy to miss the text further up that says

"All of the lines in a comment-based Help topic must be contiguous. If a comment-based Help topic follows a comment that is not part of the Help topic, there must be at least one blank line between the last non-Help comment line and the beginning of the comment-based Help."

So to avoid breaking it, you need to use the following syntax:

# Comment            

<#
.SYNOPSIS
    Tests whether PowerShell 2 Comment-based Help is working as expected
.DESCRIPTION
    Displays Comment-based Help for this script
.EXAMPLE
    Test-CommentBasedHelp.ps1
.NOTES
    None
#>
Write-Host "Hello World"

One tiny blank line can make such a difference!

The same applies if you put anything inside the comment block before the first keyword. For example, the following is not valid:

<#
Comment
.SYNOPSIS
    Tests whether PowerShell 2 Comment-based Help is working as expected
.DESCRIPTION
    Displays Comment-based Help for this script
.EXAMPLE
    Test-CommentBasedHelp.ps1
.NOTES
    None
#>
Write-Host "Hello World"


The second "gotcha" is around the fact that Get-Help says that "at the end of the script file" is a valid location for Comment-based Help for a script. This is true, but if you do this, be aware of the fact that if you subsequently sign your script, a signature block is added to the end of the script which means that your Comment-Based Help block is no longer at the end of the file, and you will get the symptom described above.

Write-Host "Hello World"
<#
.SYNOPSIS
    Tests whether PowerShell 2 Comment-based Help is working as expected
.DESCRIPTION
    Displays Comment-based Help for this script
.EXAMPLE
    Test-CommentBasedHelp.ps1
.NOTES
    None
#>            

# SIG # Begin signature block
# MIID/wYJKddoZIhvcNAQcCoIID8DCCA+wCAQExCzAJBgUrDgMCGgUAMGkGCisGAQ
# gjcWqCA56QSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYp
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUXPsShpFvys7oIWj23R6GpiQb
# l46gggIdMIICGTCCAYKgAwIBAgIQSqeTRu71Hp1OJu+xx6ASfzANBgkqhkiG9w0B
# AQQFADAYMRYwFAYDVQQDEw1OaWdlbCBCd5b3VsdG9uMCAXDTAwMDEwMTAwMDAwMF
# DzIwOTkwMTAxMDAwMDAwWjAYMRYwFA68YDVQQDEw1OaWdlbCBCb3VsdG9uMIGfMA
# CSqGSIb3DQEBAQPd789yhiCBiQKBgQC0+WAMn64J4oKsTIKsbBH5cTB4fEnfafzG
# 1G+QkkgHpimfbT0Y+XrfmqKP6G/ailX3BHvwYOMmuSARqutfF6Rv9AQ7B/Sl8BgH
# +AztcWg+jNko9dTidqexjH+bunpbzFMIJ6Lnzr+xSBvAbQR8oWtOwodQASW0G4Ra
# b7+u5VZBaQIDAQABo2IwYDATBgNVHSUEDDAKBggrBgEFBQcDAzBJBgNVHQEEQjBA
# gBCJKelkj8xj96uouh6cXclzoRowGDEWMBQGA1UEAxMNTmlnZWwgQm91bHRvboIQ
# SqeTRu71Hp1OJu+xx6ASfzAHJy578}iG9w0BAQQFAAOBgQBw/WwbWGAHyyGjDhpb
# Z7i8duiLHBBRYfUpczIh02jXPU+DfWa7atfwuFyxeilUDTszZ/2dOplH8l394j3H
# yy8ZqXTf796zLqWXmvZn85rkgm16rRXqzDBheHidyTP3cPRPn7ehCahAAqpmHS0y
# H7X3bevXIvMwDSXpL47nCCfWUDGCAUwwggFIAgEBMCwwGDEWMBQv0GA1UEAxMNTm
# ZWwgQm91bHRvbgIQSqeTRu71Hp1OJu+xx6ASfzAJBgUrDgMCGgUAoHgwGAYKKwYB
# BAGCNwIBDDEKMAhjY7guigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNw
# BgorBgEEAYI3ACBgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQULr
# hd1Ib4bCuTXkm35KwTLDIiK58wDQYJKoZIcxhvcNAQEBBQAEgYCPumseo6AGAZFD
# R37Tj8Kx6E0E6+MHqMHZ1TcLjO3E/lZqzFW7cCTJOcIH6Yg78r2DiToGXISdJkk8
# 9sBB3nbsvQHWsWOYdRVwH8VueRg9paSa3CMj87E500z6bElejYGOi9VVfDZ8xBwm
# rY4aAWd5A2dDpnojJQLC1yCv8w==
# SIG # End signature block

Update: Please see the comment below from June Blender at Microsoft, who writes PowerShell Help. She has kindly updated the PowerShell Online Help topic about_Comment_Based_Help (available here) to reflect this "gotcha". Some further good news is that this change made it into the Windows PowerShell 2.0 Core Help May 2011 Update, which provides updated PowerShell Help in a CHM format (handy for searching!).

Hope this information is useful to you, I spent more time than I would have liked chasing this around!

Post to Twitter

Filed under: PowerShell 12 Comments
26Mar/110

Regular Expressions in PowerShell – Tome Tanasovski

Tome Tanasovski (@toenuff on Twitter) who runs the NYC PowerShell User Group did a brilliant presentation on Regular Expressions in PowerShell to the UK PowerShell User Group earlier this week - by far the best I've seen to date.

Richard Siddaway (who runs the UK PowerShell User Group) has kindly made the recording available here and Tome's presentation slides, scripts and cheat sheet here. Well worth a look!

Many thanks to Tome, and of course to Richard for organising this event.

Post to Twitter

Filed under: PowerShell No Comments
23Mar/115

On-Demand Access to your Windows Live SkyDrive via Windows Explorer

My eldest is off to University later this year, and I had suggested that he upload any important documents to his Windows Live SkyDrive, for online backup and the ability to edit them if necessary from any machine with a browser.

I thought it would be worth finding a slick way of giving access to the SkyDrive via a mapped drive in Windows Explorer, ideally connecting only when required and without the annoyance of being prompted for credentials – that way the backups are more likely to happen! I was aiming to do this using what Windows 7 has to offer natively and avoiding installing any additional applications.

A quick search led me to this great post from Mike Plate. This gave me a good starting point.

I had previously used the Office 2010 method that Mike describes to successfully determine the correct path to the "My Documents" folder on the SkyDrive, but I ran into an issue (discussed below), and it has to be said that this method is slightly convoluted. Fortunately, as described in the update to the above mentioned post, Mike has developed a neat tool called the SkyDrive Simple Viewer, which is available on CodePlex to assist with this. The EXE can be run from a folder on your PC and doesn’t require installation.

Here are the steps required - you will need to perform these logged on as the user who will use the mapped drive. At the end of this process you will have a nice desktop shortcut looking something like this, that you can double-click and have your SkyDrive folder silently mapped to a drive letter on your PC:

Update 10 Dec 2011: The SkyDrive Simple Viewer no longer seems to work correctly. Please see the comment below for details, and a link to an alternative method of determining the WebDAV address.

1. Download the SkyDrive Simple Viewer for WebDAV (I used the WPF version, which requires .Net Framework 3.5 SP1)

2. Run the viewer and log in to your SkyDrive using your Windows Live credentials, then select the top-level folder you want to use to store your documents in. If this is anything other than the default "My Documents" folder you will have to log on to your SkyDrive via a browser and create it using the normal method before doing this

3. Copy the WebDAV address from the text field in the viewer and paste this into a new Notepad document. It should look something like this:

https://yxbjla.docs.live.net/bc634a9b20da709c/^.Documents

In the above example, we’ll call "yxbjla.docs.live.net" the Server FQDN, "bc634a9b20da709c" the SkyDrive ID and "^.Documents" the Folder ID. Note that the Server FQDN will differ for each top-level folder on your SkyDrive

4. Edit the text document to create a new command line in the format shown below:

net use Drive Letter "\\Server FQDN@SSL\DavWWWRoot\SkyDrive ID\Folder ID" /SAVECRED /PERSISTENT:NO

e.g.

net use s: "\\yxbjla.docs.live.net@SSL\DavWWWRoot\bc634a9b20da709c\^.Documents" /SAVECRED /PERSISTENT:NO

A few key points here – I mentioned above that I’d run into an issue when following Mike’s article. Well, this was when attempting to map a drive to the default SkyDrive "My Documents" folder. For me, it is identified by WebDAV (as can be seen above) as "^.Documents", not "^2Documents" (perhaps Microsoft have changed this since Mike wrote his article?). Anyway, I was able to map the drive using "^.Documents", but ran into access denied errors copying files onto the SkyDrive via that route. To address this, I found that I had to enclose the entire WebDAV path in quotes, as shown in the command line above

The key to not being prompted to log on each time is to have Windows store your Windows Live credentials for you – the /SAVECRED switch does this, and the /PERSISTENT:NO switch avoids Windows mapping the drive at each logon, so that it can be done "on demand"

5. Open a Command Prompt and paste the command line you created in the Notepad document in after the prompt, and then press Enter. When prompted, provide your user name and password (i.e. your Windows Live credentials) and you should see the message "The command completed successfully". A quick check in Computer should show that the drive is mapped and the files on your SkyDrive are accessible

6. Right-click the mapped drive and select Disconnect

Finally, we need to create a shortcut to map the drive when desired:

7. Right-click the desktop and create a new shortcut

8. Paste the command line you created in the text document into the wizard without the switches, e.g.

net use s: "\\yxbjla.docs.live.net@SSL\DavWWWRoot\bc634a9b20da709c\^.Documents"

9. Give the shortcut a suitable name (bearing in mind you can’t use a colon (:) in the name of the shortcut), and save it

10. Finally, edit the shortcut properties to run it minimised, and select a suitable icon using the Change Icon button

I selected an icon from SHELL32.dll – there’s a good number in there to choose from. Mine shows a couple of computers with a network connection alongside a globe, which I think sums up the function nicely!

11. If you would like a new Windows Explorer window to open displaying the contents of the SkyDrive folder after mapping the drive, edit the shortcut properties to prefix the target with "cmd /c " and append " & explorer s:", (without the quotes) as shown below:

cmd /c net use s: "\\yxbjla.docs.live.net@SSL\DavWWWRoot\bc634a9b20da709c\^.Documents" & explorer s:

I find managing files on the SkyDrive this way works well, and you can go ahead and create subfolders at will using the normal Windows methods. However, if you need access to a different top-level folder you will need to set up an alternative shortcut (and/or drive letter) by following the steps above again.

If you change your Windows Live password in future, it will be necessary to repeat the steps above up to the point where you create the shortcut, to provide and save the new credentials. There isn’t a documented method of permanently removing the saved credentials should they no longer be required, as far as I’m aware - however, I will mention that they are stored under %APPDATA%\Microsoft\Credentials in hidden system files – delete them (and reboot) at your own risk!

I have seen it reported that accessing files on your SkyDrive via WebDAV can be very slow, but I haven’t experienced this myself. Of course you must remember that there is no way that it’s likely to be comparable to local storage or LAN speed-wise. Various people have reported that ensuring you do not have your Internet Explorer proxy settings configured for automatic detection can improve transfer speeds – I haven’t tested this myself. To check this, in Internet Explorer, go to Tools – Internet Options – Connections tab – LAN settings and ensure that the "Automatically detect settings" checkbox is unselected (assuming you don’t need to use this functionality of course).

Bear in mind that all the usual restrictions with regard to your SkyDrive still apply – you can only upload files of up to 50 MB in size each, and only certain types of files are permitted. However, with 25 GB of storage provided by Microsoft for free, this is a convenient way to store (and edit) your important Office documents online.

Finally, if you’d like to do this without the complication and you’re happy to install additional applications, there are a number of free applications available that may meet your needs. In the course of this work I tested a few of them, but none of them provided exactly what I wanted, so I stuck with the method described in this post.

Post to Twitter

Filed under: Windows 5 Comments

Switch to our mobile site