Are Windows Administrators Less Likely to Script/Automate?

I wrote this bit as a comment on another blog this morning, and then after typing a thousand words I realized that it would make good fodder as a post on my own blog. The article that I was commenting on was on the topic that Windows administrators are less likely to script and/or automate because Windows uses a GUI and Linux is more CLI-centric. And because Linux is more CLI-focused, it is more natural for a Linux user to get into scripting than it is a Windows user. Without further ado, here is my comment on that article:

This article and these comments suffer from a lack of the presence of a good Microsoft engineer and/or administrator. As is common, this discussion so far has been a bunch of Linux admins complaining that Windows isn't more like Linux, but not offering much substance to the discussion from a pro-Microsoft perspective.

That said, I may be a Microsoft zealot, but I do understand and appreciate Linux. I think it’s a great, fast, modular, infinitely customizable and programmable operating system. So please don’t read this in anger, you Linux admins.

First I want to stay on track and pay respect to the original article, about scripting on Windows. Scripting has been an integral part of enterprise-grade Windows administration ever since Windows entered the enterprise ecosystem. It has evolved and gotten a lot better, especially since Powershell came on the scene, and it will continue to evolve and get better, but we've already been scripting and automating Windows in the enterprise space since the '90s. (Though maybe not as well as we would have liked back then.)

But I will make a concession. There are Windows admins out there that don't script. A lot of them. And I do view that as a problem. Here's the thing: I believe that what made Windows so popular in the first place - its accessibility and ease of use because of its GUI - is also what leads to Windows being frequently misused and misconfigured by unskilled Windows administrators. And that, in turn, leads people to blame Windows itself for problems that could have been avoided had you hired a better engineer or admin to do the job in the first place.

GUIs and wizards are nice and have always been a mainstay of Windows, but I won’t hire an engineer or administrator without good scripting abilities. Even on Windows you should not only know how to script, but you should want to script and have the innate desire to automate. If you find yourself sitting there clicking the same button over and over again, it should be natural for you to want to find a way to make that happen automatically so you can go do other more interesting things. Now that I think about it, that’s a positive character trait that applies universally to any occupation in the world.

And yeah, it was true for a long time that there were certain things in Windows that could only be accomplished via the GUI. But that’s changing – and quickly. For instance, Exchange Server is already fully converted to the point where every action you take in the GUI management console is just executing Powershell commands in the background. There’s even a little button in the management console that will give you a preview of the Powershell commands that will be executed if you hit the ‘OK’ button to commit your changes. SQL Server 2012 will be the first version of SQL that’ll go onto Server Core. (About time.) The list goes on, but the point is that Microsoft is definitely moving in the right direction by realizing that the command line is (and always has been) the way to go for creating an automatable server OS. Microsoft is continuing to put tons of effort into that as we speak.

However, just because scripting on Windows is getting better now doesn’t mean we haven’t already been writing batch files and VB scripts for a long time that do pretty impressive things, like migrate 10,000 employee profiles for an AD domain migration.

I really love Server Core, but it's just a GUI-less configuration of the same Windows we've been using all along. Any decent Windows admin has no trouble using Core, because the command line isn't scary or foreign to them. For instance, one of the comments on this article reads:

"The root of the problem seems to be that Linux started with the command line and added GUIs later, whereas Windows did it the other way around."

I think that's false. Windows started as a shell on top on top of DOS – a command line-only operating system. DOS was still the underpinning of Windows for a long time and even after Windows was re-architected and separated from DOS, the Command Prompt and command-line tools were and still are indispensible. Now I will grant you that Linux had way better shells and shell scripting capabilities than Windows did for a long time, and Microsoft did have to play catch-up in that area. Powershell and Server Core came along later and augmented the capabilities of and possibilities for Windows – but the fact remains we’ve been scripting and automating things using batch files and VB Script for a long time now.

There was also this comment: “Another cause for slow uptake is that Windows skills don't persist.”

Again I would say false. I can run a script I wrote in 1996 on Server 2012 just fine, with no modification. Have certain tools and functions evolved while others have been deprecated? Of course. Maybe a new version of Exchange came out with new buttons to click? Of course – that’s the evolution of technology. But your core skillset isn’t rendered irrelevant every time a new version of the software comes out. Not unless your skillset is very small and narrow.

There was also this comment:

“I also complain that PowerShell is not a "shell" in a traditional sense. It is not a means of fully interacting with the OS. There is no ecosystem of text editors, mail clients, and other tools that are needed in the daily operation and administration of servers and even clients.”

As I mentioned earlier, there are fewer and fewer things every day that cannot be done directly from Powershell or even regular command-line executables. And to the second sentence - I’m not sure if there will ever be a desire to go back to an MS-DOS Edit.exe style text editor or email clients… but I could probably write you a Powershell-based email client in an hour if you really wanted to read your emails with no formatted styles or fonts. :)

So in the end, I think the original article had a good point - there probably are, or were, more Linux admins out there with scripting abilities than Windows admins. But I also think that's in flux and Windows Server is poised in a better position than ever for the server market.

Transitioning From VB Script to Powershell

VB Script is still around and will be for quite a while yet.  But current Windows technology is all about Powershell.  As well it should be, as PS is vastly superior in many, many ways.

However, a lot of us still have old VB scripts hanging around, probably doing production work... and what I’m about to show you may be the trickiest part of porting those old scripts over into Powershell. 

As you probably know, Powershell fully harnesses the power and flexibility of .NET, while VB Script was only capable of working with COM objects.  Almost everything that can be done with COM objects can be done faster and easier with .NET.  (For the foreseeable future at least - I hear COM is making a bit of a comeback in Windows 8...)  However, Powershell is still fully capable of working with COM objects too.  What that means is that those of you who are still more comfortable with VB script or have a lot of script to port over in a hurry, well, you don’t have to worry about finding .NET equivalents for those COM objects. (Even if there might be a better, more Powershell-native way of doing it.) 

Let’s take Microsoft Cluster Services for example.  Here’s what you would see in a VB script that deals with cluster resources: 


Set oCluster = CreateObject("MSCluster.Cluster")


 In Powershell it’d be something like this: 


$cluster = New-Object –COMObject MSCluster.Cluster


 Now  you have your cluster object.  Want to see what all members it has?  (The properties of it + its methods/what all it can do?) 


$cluster | Get-Member


 Alright well I see that $cluster is basically an object collection that has, among other things, a ResourceGroups object in it, so let’s open that up: 


$ResourceGroups = $cluster.ResourceGroups


 And then do a $ResourceGroups | Get-Member to see what we can do with that: 


PS C:\Users\ryan> $resourceGroups | Get-Member

   TypeName: System.__ComObject#{f2e60706-2631-11d1-89f1-00a0c90d061e}

Name                MemberType Definition
----                ---------- ----------
Delete              Method     void Delete ()
Move                Method     Variant Move (Variant, Variant)
Offline             Method     Variant Offline (Variant)
Online              Method     Variant Online (Variant, Variant)
Cluster             Property   ISCluster Cluster () {get}
CommonProperties    Property   ISClusProperties CommonProperties () {get}
CommonROProperties  Property   ISClusProperties CommonROProperties () {get}
Handle              Property   ULONG_PTR Handle () {get}
Name                Property   string Name () {get} {set}
OwnerNode           Property   ISClusNode OwnerNode () {get}
PreferredOwnerNodes Property   ISClusResGroupPreferredOwnerNodes PreferredOwnerNodes () {get}
PrivateProperties   Property   ISClusProperties PrivateProperties () {get}
PrivateROProperties Property   ISClusProperties PrivateROProperties () {get}
Resources           Property   ISClusResGroupResources Resources () {get}
State               Property   CLUSTER_GROUP_STATE State () {get}

So hopefully this is starting to pique your interest.  With this sort of information you could easily script out whether all the cluster resource groups were on the correct nodes, and even move them if need be.  Pretty neat stuff.

I leave you with this - don't you hate it when this happens?

F'ed up log

A List of NICs, IPs, MACs, Physical Locations, etc.

I'm back, finally.

I was recently challenged with trying to not only enumerate all the network adapters on a system across dozens of different operating system versions and hardware platforms, but also to try to figure out where they are physically in the machine, remotely, without being able to see the actual hardware.

The short answer is you can't.

The long answer is you can't... do it scriptomatically without the assistance of vendor-specific software, such as the HP network configuration software and maybe an API or WBEM queries... but that's only going to cover one specific hardware platform. I need to consistently gather this data across not only Proliants, but Poweredges, VMs, desktop workstations, anything that runs Windows. Windows doesn't know where in space your network adapters are. By that I mean Windows doesn't know which physical port on your 4-port NIC is the third one from the left, etc. This would be why there is seemingly no rhyme or reason as to which network adapter Windows assigns "Local Area Network", "Local Area Network #2", "Local Area Network #3", etc. The installed NICs are enumerated randomly, as evidenced by the fact that you may get different results for which NIC port is assigned to which network connection every time you re-install Windows on a multi-NIC machine. I have heard that some particularly anal administrators even go so far as to install Windows, then delete all the Network Connections that are out of order, and continue removing and letting Windows reinstall them until they are all in the "correct" order. There is also a theory that NIC manufacturers of multi-port NICs should give each port on the card sequential MAC addresses, starting from the port closest to the PCI bus. So you might be able to infer something from that, but that's not something I would put money on for thousands of NICs with dozens of manufacturers.

Furthermore, "NIC teaming" throws yet another wrench into this, as now you can no longer rely on what Windows thinks the MAC address of a teamed adapter is, or what the cabinet switch thinks the MAC address is on a given switch port that has a teamed NIC plugged in to it.

I can get you all the information that Windows does have though, including (apparent) MAC addresses, IPs, and "Location Information" as read from the registry. This is that "Bus 0, Device 8, Function 25" stuff that you might have seen in Device Manager. It might be useful in drawing some correlations, but it's still not going to tell you much about physically where all these NICs are.

So without further ado, here are the scripts. The first one is Powershell. The second one is the exact same but ported to VB Script, for compatibility with older versions of Windows. Note the operating system version check in the VB Script.


$ErrorActionPreference = 'Stop'
$nics = Get-WmiObject Win32_NetworkAdapter
$cfgs = Get-WmiObject Win32_NetworkAdapterConfiguration

Write-Host "`nPhysical NICs In No Particular Order"
Write-Host "------------------------------------`n"
foreach ($_ in $nics)
			$registryKey = Get-Item HKLM:\System\CurrentControlSet\Enum\$($_.PNPDeviceID)
			$keyValues   = Get-ItemProperty $registryKey.PSPath
			$regSplit    = $keyValues.LocationInformation.Split(";") 
			$location    = $regSplit[2].Replace('(','').Replace(')','')
			$locSplit    = $location.Split(",")			
			Write-Host "Name    : $($_.Name)"
			Write-Host "MAC     : $($_.MACAddress)"
			Write-Host "Location: Bus $($locSplit[0]), Device $($locSplit[1])`, Function $($locSplit[2])"
			$mac = $_.MACAddress
			foreach ($cfg in $cfgs)
				if($cfg.MACAddress -eq $mac -And $cfg.IPAddress)
					Write-Host "IP      : $($cfg.IPAddress)"
			Write-Host " "	
	Catch {	}

VB Script:

Option Explicit
const HKEY_LOCAL_MACHINE = &H80000002

Dim nic, objNICs, objCfgs, objWMIService, objReg, objOSVer
Dim strWMIQuery, strRegistryKey, strValue, strLocInfo, strBus, strDevice, strFunction, strOSMajor
Dim arrSplitKey, arrSplitLoc, arrOSBuild
Dim mac, cfg, ip, v

strWMIQuery = "SELECT * FROM Win32_NetworkAdapter"
Set objWMIService = GetObject("winmgmts:\\.\root\CIMv2")
Set objNICs = objWMIService.ExecQuery(strWMIQuery)
strWMIQuery = "SELECT MACAddress,IPAddress FROM Win32_NetworkAdapterConfiguration"
Set objCfgs = objWMIService.ExecQuery(strWMIQuery)
strWMIQuery = "SELECT Version FROM Win32_OperatingSystem"
Set objOSVer = objWMIService.ExecQuery(strWMIQuery)

For Each v in objOSVer
	arrOSBuild = Split(v.Version,".")

strOSMajor = arrOSBuild(0)

Wscript.Echo "Physical NICs In No Particular Order"
Wscript.Echo "------------------------------------"

For Each nic In objNICs
	If StrComp(Left(nic.PNPDeviceID,3),"PCI",1) = 0 Then
		Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")
		strRegistryKey = "System\CurrentControlSet\Enum\" & nic.PNPDeviceID				
		objReg.GetStringValue HKEY_LOCAL_MACHINE,strRegistryKey,"LocationInformation",strValue
		If CInt(strOSMajor) >= 6 Then
			arrSplitKey = Split(strValue,";")
			strLocInfo = arrSplitKey(2)
			strLocInfo = Replace(strLocInfo,"(","")
			strLocInfo = Replace(strLocInfo,")","")
			arrSplitLoc = Split(strLocInfo,",")
		End If
		Wscript.Echo "Name    : " & nic.Name
		Wscript.Echo "MAC     : " & nic.MACAddress
		If CInt(strOSMajor) >= 6 Then
			Wscript.Echo "Location: Bus " & arrSplitLoc(0) & ", Device " & arrSplitLoc(1) & ", Function " & arrSplitLoc(2)
			Wscript.Echo "Location: " & strValue
		End If
		mac = nic.MACAddress
		For Each cfg In objCfgs
			If StrComp(cfg.MACAddress,mac) = 0 And isNull(cfg.IPAddress) = False Then
				For Each ip In cfg.IPAddress
					Wscript.Echo "IP      : " & ip
			End If
		Wscript.Echo " "
		If isObject(objReg) Then Set objReg = Nothing
	End If

The output looks like this:

The IPs are not shown on the second adapter because it's switched off right now and thus doesn't have any IPs. My first idea for improvement of the Powershell version (I don't invest much time into improving VBS,) is making custom objects out of the output instead of just doing Write-Hosts. The power of Powershell is in its ability to deal with objects, and so you should try to keep everything as objects for as long as possible. Once you've spit it out on the screen in a Write-Host statement for example, you can no longer pass it along the pipeline, etc.

Thanks to Kelvin Wong and Server Fault for helping me research this.