More tales from the weekend of PowerShell: a bit of a horror story

I mentioned my weekend of PowerShell, and that I've been working on scripts for the Office Communications Server 2007 resource kit.

One of the things we learnt in Live Communications Server 2005 was that TLS certificates caused a lot of grief either

  • They were issued by an internal Certification Authority, which wasn't trusted.
  • Clock differences mean the certificate wasn't yet valid
  • The wrong names were on the certificate
  • The certificate wasn't marked for the right enhanced key usages.

So one of the requests was to have a function which showed the certificate. PowerShell has a certificates provider so you can get at them... this shouldn't be too hard should it ? Ha. Ha. Ha. The function I wrote looked like something from an obfuscation contest.

 function List-OCSSipRoutingCert
{$sn=''
 (Get-WmiObject -class MSFT_SIPRoutingSetting).tlsCertSN | 
      foreach { if ($_  -lt 16) {"0{0:x}" -f $_} else {"{0:x}" -f $_} } | foreach { $sn = $_ + $sn  }
 dir cert:\localmachine\my | where {$_.serialnumber -eq $sn} | 
     format-list -property FriendlyName,Subject, Issuer, NotBefore, NotAfter,@{label="Enhanced key Usage"; 
         expression={$_.Extensions | 
                     foreach { if ($_ -is [System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]) 
                              {$_.EnhancedKeyUsages|foreach {$_.FriendlyName}  } 
                         } 
                }}
}

The first bit is simple enough. Get a WMI object which has a property for the TLS Certificate Serial number. And  the DIR ...| Where finds the object on the CERT: "drive" with that serial number.  And  format-list will output it. But ...  foreach { if ($_ -lt 16) {"0{0:x}" -f $_} else {"{0:x}" -f $_} } | foreach { $sn = $_ + $sn } ... what's that.

Well: PowerShell's CERT: provider returns .NET certificate objects, and their serial numbers are strings containing big hex numbers. But the WMI object doesn't return a string for the serial number; that would be way too simple. It returns an array of integers, least significant first (that's backwards to most people) So we need to convert the integers to 2 hex digits and assemble them into a string - the assembly is done by  foreach { $sn = $_ + $sn } , but how do I take "10" and convert it to "0A" ? It's obvious from the flow of the code that it happens in this bit:   { if ($_ -lt 16) {"0{0:x}" -f $_} else {"{0:x}" -f $_} .  For all my talking about the effectiveness, no the beauty, the elegance, of PowerShell, this isn't not elegant at all. What is going on there ? 

Technet's VB to PowerShell conversion page which I've praised before said in place of  HEX use "{$:x}" -f $_. What possesses someone to allow a string on its own to take a parameter: in my mind only functions/procedures/methods take parameters;  not in PowerShell.  Could I find how to pad to two digits ?  Could I  [expletive deleted]! "{$:00}" -f $_  will pad to 2 decimal digits, but $:xx outputs "xx". Which led to me writing that ugly code.

Maybe it is that, as I said before basic has scarred me, but it was only afterwards that it dawned on me... PowerShell strings are basically .net System.String objects;  sending one into get-member shows I have most of the methods that Visual Studio shows for system.string (padleft, trim , replace etc) but not format: I don't know why it is implemented with this -f. When I started thinking like this I thought, instead of looking for a powershell answer I'd look for a .net one, At the bottom of  MSDN's article on the System.string format method, is something which says Kathy Kam has more... and it's the only good reference I've found; too late of course, the code was done. She even has a section on leading zeros! And to put the tin lid on it all she says "Now, that we've gone through the valid specifiers, you can actually use this in more than just String.Format(). For example, when using this with Byte.ToString()"   So I didn't need to use this -f nonsense with double foreach loops. I could have written

| foreach {$sn = $_.toString("x2") + $sn }

Now that - to my Basic-scarred mind - is elegant.  I've got half a dozen List-xxxxCertificate functions and I may go back and change them: this is, after all, supposed to be code that people can re-work for themselves. The format-list command has to do some pretty ugly work to find the EKU attribute of the certificate and then output each of the friendly names for it. If I do go back I'll add subject alternate names which (I think) work in a similar way.

Two other Uglinesses I have found are worth a mention both concern remote WMI.

One concerns calling Get-WMiobject. It takes umbrage if passed a -credential parameter when running against the local machine so I typically have code like this.

 function Get-OcsWindowsServices
{Param($Server , $user)
 if (($server -ne $null) -and ($server -ne '.'))
    {Get-WmiObject -query "select * from win32_service where name like 'RTC%' " -computername $server -credential $user}
 else 
    {Get-WmiObject -query "select * from win32_service where name like 'RTC%' "}
}

It occurred to me, too late on in the process to change a lot of functions, that I should probably default an empty server field to "." - the current server, and simplify the if condition. i.e.

 Param($Server='.' , $user)
if  ($server -eq '.')  {Get-WmiObject -query "select * from win32_service where name like 'RTC%' "}
else {Get-WmiObject -query "select * from win32_service where name like 'RTC%' " -computername $server -credential $user}

The other is that Powershell doesn't have a New-wmiObject command, and both of the methods I've seen for creating one seem only to work on local servers. (Sits back and waits for someone to post a sample that does)

 

 

Technorati tags: Microsoft, Powershell, office communications server