Auto-install of .Net 3.5 on Windows 2012 R2

The “standard” way of installing .Net 3.5 into a Windows 2012 R2 server, is to mount the install DVD (or ISO image), and use Add Features to install it. Obviously this is a massive pain if you’ve got a lot to do, as you either need to copy 4.5Gb of image around, or use some out-of-band method of mounting the image, neither of which are ideal. The only sane option would be to extract the ISO to a CIFS share, and make that available to all servers, but this wasn’t an option here.

For automation, we would normally use the PowerShell command:

Install-WindowsFeature Net-Framework-Core -source \\image-path\sources\sxs

… so already, it looks like we don’t need all the image to do the install, just the “sources\sxs” directory.

A quick check shows that the “sources\sxs” directory is 289Mb, so much more manageable, but surely we can do better than this, as it includes a lot of other features.

Running a filter with procmon during the feature install allows you to capture all the file accesses to the sources\sxs directory, which can be exported as a CSV file:
"Time of Day","Process Name","PID","Operation","Path","Result","Detail"
"1:59:25.0591131 PM","TiWorker.exe","2896","ReadFile","D:","SUCCESS","Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal"
"1:59:25.1140710 PM","TiWorker.exe","2896","ReadFile","D:","SUCCESS","Offset: 4,096, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal"
"1:59:25.1183118 PM","TiWorker.exe","2896","ReadFile","D:","SUCCESS","Offset: 8,192, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal"
"1:59:25.1197002 PM","TiWorker.exe","2896","CreateFile","D:\sources\sxs","SUCCESS","Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, Impersonating: NT AUTHORITY\SYSTEM, OpenResult: Opened"
"1:59:25.1215631 PM","TiWorker.exe","2896","QueryBasicInformationFile","D:\sources\sxs","SUCCESS","CreationTime: 3/21/2014 2:27:47 PM, LastAccessTime: 3/21/2014 2:27:47 PM, LastWriteTime: 3/21/2014 2:27:47 PM, ChangeTime: 3/21/2014 2:27:47 PM, FileAttributes: RD"
"1:59:25.1215789 PM","TiWorker.exe","2896","CloseFile","D:\sources\sxs","SUCCESS",""
"1:59:30.8039209 PM","TiWorker.exe","2896","ReadFile","D:","SUCCESS","Offset: 0, Length: 4,096, I/O Flags: Non-cached, Paging I/O, Synchronous Paging I/O, Priority: Normal"
...etc....

This can then be condensed with (I’m using a *nix command line to do this as it’s more familiar to me):

cat Logfile.CSV | cut -d"," -f5 | cut -d"\\" -f4 | sort -u | grep -v -e "^$" -e "^\""

To give a list of subfolders in the “sources\sxs” directory that are required. These can be used to copy the relevant source files to a zip archive:
cat Logfile.CSV | cut -d"," -f5 | cut -d"\\" -f4 | sort -u | grep -v -e "^$" -e "^\"" | while read folder
do
zip -r net35.zip /Volumes/Win2012R2ISO/sources/sxs/$folder
done

…which generates an (approx) 88Mb zip file, much more suitable for installing via automation.

It’s then a fairly straightforward task to use your automation framework (Chef, Puppet etc) to copy the zip file down to the server, extract and run the powershell command to install.

PowerCLI code snippet to get storage driver details

This is just a brief post to share a code snippet that I built to display the storage driver in use.

The driver and it’s version are critical for VMware VSAN, and I needed a quick and easy way of checking it. I might revise the code at a later date to run across multiple hosts in a cluster, and output the results in a table, but for now, here’s the basics.

connect-viserver <vcname>
$esxcli = Get-EsxCli -vmhost <esxihostname>
$adapter = $esxcli.storage.core.adapter.list() |
select Description,Driver,HBAName | where {$_.HBAName -match "vmhba0"}
$driver = $adapter.Driver -replace "_", "-"
$esxcli.software.vib.list() |
Select Name,Version,Vendor,ID,AcceptanceLevel,InstallDate,ReleaseDate,Status |
Where {$_.Name -match ($driver + "$")}

This displays output such as

Name            : scsi-megaraid-sas
Version         : 6.603.55.00-1OEM.550.0.0.1331820
Vendor          : LSI
ID              : LSI_bootbank_scsi-megaraid-sas_6.603.55.00-1OEM.550.0.0.1331820
AcceptanceLevel : VMwareCertified
InstallDate     : 2016-05-03
ReleaseDate     :
Status          :

This works for the servers I’ve tried it on (Dell) but as usual YMMV…

Automating NSX from PowerCLI

I’ve been working on an NSX-based project recently, and given the task of automating the addition of new DLR Logical Switches and Edge devices.

After a discussion around the alternatives with colleagues, we decided the best way forward (for now) was to do it in PowerShell/PowerCLI, and a quick google found Chris Wahl’s post here

This was a great basis to work from (Thanks Chris!), but lacked a number of things I needed: Creating DHCP Pools, attaching a Logical Switch to an existing Edge device, and some relatively minor amendments to DLR/Edge configurations.

Of these the attachment of a new LS to an existing Edge proved the most intellectually taxing, as Chris’ scripts work with building new raw XML to PUT/POST with the REST API, and I soon discovered that the only way to amend an Edge configuration through the REST API is to pull the existing config as an XML, amend it, and PUT it back.

On top of this, the XML retrieved through the “Invoke-WebRequest” PowerShell cmdlet is of type “System.Xml.XmlElement” whereas to do things like “CreateElement” – which we need to do to add new entries into the configuration –  it needs to be of type “System.Xml.XmlDocument”.

After a number of failed workarounds, I found that dumping the XML to a file, and reimporting, gave me the XML in the correct object type – this is a little ugly though, and while the automation is for something that would only be used occasionally, I don’t like ugly hacks in my code!

A little more effort, and I had a suitable alternative – casting the XML to a string object and back to an XML object yielded the result I was looking for.

$edge = Invoke-WebRequest -Uri “$uri/api/4.0/edges/$routerid” -Headers $head -ContentType “application/xml” -ErrorAction:Stop
 [xml]$edgexml = $edge.Content
 $textxml = $edgexml.innerxml
 [xml]$body = $textxml

I could then work with $body as a normal XmlDocument object in PowerShell.

The next issue I had was making the amendments to the XML.

First – make sure the new Logical Switch is not already attached, and then find the first unused interface:

foreach ($vnic in $body.edge.vnics.vnic) {
     if ($vnic.name -match $config.newLS.name) {
          $attached = "true"
          Write-Host -BackgroundColor:Black -ForegroundColor:Red "Warning: $($config.newLS.name) already attached. Skipping."
          break
     if ($vnic.isConnected -match "false") {

Second – setting values for XML entities that were already in the XML. Easy:

$vnic.name = $config.newLS.name
$vnic.isConnected = "true"

Third – adding a new XML entity that wasn’t already there:

$elem = $body.CreateElement("portgroupId")
$vnic.AppendChild($elem)
$vnic.portgroupId = $switchvwire.get_Item($config.newLS.name)

Finally – adding entities to an empty node “<addressGroups />”. Not so easy! This took some considerable time, including many false starts! In the end I discovered that to “find” the empty node using SelectSingleNode I had to set up a namespace. Then I could find it and remove it (this seemed easier than trying to attach entries to the empty node). Then I could create some raw XML and attach it into the Edge configuration XML using ImportNode and AppendChild.

$ns = New-Object -TypeName System.Xml.XmlNamespaceManager -ArgumentList $body.NameTable
$ns.AddNamespace("ns",$body.DocumentElement.NamespaceURI)
$oldAddressGroups = $vnic.SelectSingleNode("//vnic[index=$inf]/addressGroups")
$vnic.RemoveChild($oldAddressGroups)
[xml] $addr = "<addressGroups>
  <addressGroup>
    <primaryAddress>$($config.newLS.edgeip)</primaryAddress>
    <subnetMask>$($config.newLS.mask)</subnetMask>
  </addressGroup>
</addressGroups>"
$vnic.AppendChild($body.ImportNode($addr.addressGroups, $true))

Once that was done all I had to do was send the XML back using Invoke-WebRequest

# Attach new logical switch to existing Edge
try {$r = Invoke-WebRequest -Uri "$uri/api/4.0/edges/$routerid" -Body $body -Method:Put -Headers $head -ContentType "application/xml" -ErrorAction:Stop -TimeoutSec 30} catch {Failure}
if ($r.StatusCode -match "204") {Write-Host -BackgroundColor:Black -ForegroundColor:Green "Status: Successfully attached new Logical Switch to $($config.edge.name)."}
else {
$body
throw "Was not able to add new Logical Switch to existing Edge. API status code was not 204."
}
break}

I’ve no doubt that there are probably some better ways of achieving some of what I’ve done here, but I thought I would post it up in case anyone is looking to do something similar.