Monday, January 6, 2014

Dastardly Dissections: When Scripting is just one Tool, not a Whole Tool Box

(Note: This may be the first of a new series of blognostications I'm thinking of calling "Dastardly Dissections".  If I maintain my motivation, and caffeine intake, I will strive to use real world examples where technology was used to solve problems.  Other than the kinds of technology that blow things up, that is).


For many IT folks, or folk, as they say in places I've never been, this may step into the murky, sticky muddy waters of borderline sacrilege, but give me a chance.  I know a lot of scripters, script-kiddies, skittles-munching, bong-drinking coder geeks like to think of scripts as the holy grail to solving all IT challenges.  And for many situations they can be very well-suited to that purpose indeed.  But for me, I find it more interesting when script code plays the part of Mr. Glue (okay, Ms. Glue is fine too), and helps bind the forces of the technoverse into harmony.  Kind of like magical fiber in the ethereal digestive tract of the processing world.  Um, yeah.

Case in Point:  Crapware needs a Crutch.

IT folks who get their fingers all sticky with repackaging and deployment should be well familiar with less-than-ideal packaging chores.  One that comes to mind (and, NO, I'm not going to name the vendor or product.  Mainly out of pants-wetting fear of law suits), is a software application that comes with a digital signature pad.

Those are the kinds of detachable devices where you whip out a plastic pen-like thing, often with an attached cable thing, which is then grasped by the authoritative human with firm, yet shaky hands to apply a soon-to-become-extinct thing called a "handwritten signature".  I'm told people used to actually scribble words using their own hands to indicate who they are (or were).  Amazing!  I wonder how long it will be before it's commonplace to have a chip implanted at birth.  Moving along...

Ok.  I'll get to the point.

First Challenge:  The software probes for the peripheral signature pad device connection and determines the COM port assignment.  From that it writes a new .INI configuration file (yes, this is supposedly "Windows XP compliant, and they say, that it should work fine on Windows 7).  It does this the first time it is plugged in and always uses that port from then on (as long as the SAME device is attached to the SAME computer with the SAME operating system configuration).

Bigger challenge:  The customer purchased roughly 400 of these things.  They need to be handed out to the field-workers, who each have a laptop running on Windows 7 Enterprise SP1.  They rarely connect directly to the organization network, and they need to be able to plug these things in, and use them, while in the field.  That means they are most often NOT on a VPN connection or connected to the Active Directory domain environment. But, that's not all!

Bigger than Bigger challenge:  The INI file is created in, and will only work from within the C:\WINDOWS folder (e.g. %WINDIR%).  That means UAC is giving them the big face-smack of security each time.  KaBlam!! Thwack!  Kazowwy!  sorry, no more of that...

What to do?

Well, here's ONE way that happens to work:
  1. Perform a "manual" installation of the crapware application, or as some might call it: crapplication.
  2. Plug the device in.  Wait for the UAC bitch-slap.
  3. Provide the suitable credentials to allow the crap-device to talk to the crapplication and update the crappy INI setting.
  4. Save a copy of the INI file.
  5. Locate the line which stores the COM port setting.  Replace the value (e.g. "CrappyComPort=COM3") with a unique string value (e.g. "CrappyComPort=ZZZZZ") and save the copy.
Example of copied INI "template" file...

[Tablet]
TabletComPort=ZZZZ
TabletType=0
TabletLCDMode=0
TabletModel=Crap_VR549
...

Now, using (insert dramatic entrance music) script code, you pull on the chains of the old "DEVCON" command-line utility to probe for the device and COM port.  Write the results to a text file (redirect or whatever you prefer).  I'm sure you can probe it using WMI or crawling the Registry as well, but since I like old commands, because I'm also old, I go that route.

[updated 1/7/2014]
As for the specific DEVCON syntax (thanks to Chris for this by the way)...

    devcon.exe" hwids *FTDIBUS* >>"%Windir%\CrapWare\devcon_dump.txt"

Next, using script code, open and parse the DEVCON output file to get the COM port number.  Then open the copied INI file, read in the guts, replace the "ZZZZ" (or whatever you chose to use) with the "COMx" value, and write it back out to the C:\WINDOWS\crapplication.INI file, all under a suitable elevated user context (i.e. SYSTEM, would be fine), and let her rip!

Example of the DEVCON dump file...

FTDIBUS\VID_0403+PID_6001+A983498VX\0000
  Name: USB Serial Port (COM4)
  Hardware ID's:
    FTDIBUS\COMPORT&VID_0403&PID_6001
1 matching device(s) found.


Special thanks to my colleague and coworker extraordinaire: Chris.  Because, he is extraordinary.  And as they say, the difference between ordinary, and extraordinary, is that extra bit.

The dastardly code...

[beginnen der codenhauzen]

'****************************************************************
' Filename..: crapplication.vbs
' Author....: INSERT YOUR NAME HERE UNLESS IT BLOWS UP (then use another name)
' Date......: 01/05/2014
' Purpose...: parse text file to obtain desired value, win the prize!
'****************************************************************
Option Explicit

Dim scriptPath, strTemplate, objFSO, objFile
Dim strLine, strPart, pos, strComPort, ts, t1
Dim objINIsource, strNew, strAll, objShell

t1 = Timer
scriptPath = Replace(wscript.ScriptFullName, "\" & wscript.ScriptName, "")

strTemplate = scriptPath & "\crapware.ini"

Const inputFile  = "c:\windows\temp\devcon_dump.txt"
Const outputfile = "c:\windows\crapware.ini"
 
Const ForReading = 1
Const ForWriting = 2
Const TristateUseDefault = -2
Const TriStateTrue    = -1
Const TriStateFalse   = 0

wscript.echo "info: devcon file == " & inputfile
wscript.echo "info: output file == " & outputfile
wscript.echo "info: template == " & strTemplate

strComPort = ""
' comment: read the COM port from the devcon output file...
Set objFSO  = CreateObject("Scripting.FileSystemObject")
On Error Resume Next
Set objFile = objFSO.OpenTextFile(inputFile, ForReading)
If err.Number = 0 Then
 Do Until objFile.AtEndOfStream
  strLine = Trim(objFile.Readline)
  pos = InStr(strLine, "(COM")
  If pos > 0 Then
   strPart = Trim(Mid(strLine, pos))
   ' comment: should return "(COMx)"
   strPart = Replace(Replace(strPart, "(", ""), ")", "")
   ' comment: should be left with "COMx"
   strComPort = Replace(strPart, "COM", "")
  End If
 Loop
 
 objFile.Close
 
Else
 wscript.echo "fail: error (" & err.Number & ") = " & err.Description
End If

wscript.echo "info: current COM port: " & strComPort
' comment: open and suck-in all text from the template file...
Set objINISource = objFSO.OpenTextFile(strTemplate, ForReading, False)
strAll = objINIsource.ReadAll()
strNew = Replace(strAll, "TabletComPort=ZZZZ", "TabletComPort=" & strComPort)

' comment: get the vendor's crappy ini or make a new one...

On Error Resume Next
Set objShell = CreateObject("Wscript.Shell")

wscript.echo "info: searching for default ini file... " & outputfile
Set objFile = objFSO.GetFile(outputFile)
If err.number <> 0 Then
 err.Clear
 wscript.echo "info: ini file not found. creating new one..."
 wscript.echo "info: creating new file: " & outputfile
 Set ts = objFSO.CreateTextFile(outputfile)
 If err.Number <> 0 Then
  wscript.echo "error: " & err.number & ": " & err.Description
  Set objFSO = Nothing
  wscript.quit err.Number
 End If
 wscript.echo "info: waiting for a second. why rush things?..."
 objShell.Sleep 1000
 Set objShell = Nothing
 ts.Write strNew
 ts.Close
 wscript.echo "info: file created successfully!"
Else
 err.Clear
 wscript.echo "info: deleting old file... " & outputfile
 objFSO.DeleteFile outputfile, True
 wscript.echo "info: creating new file... " & outputfile
 Set ts = objFSO.CreateTextFile(outputfile)
 If err.Number <> 0 Then
  wscript.echo "error: " & err.number & ": " & err.Description
  Set objFSO = Nothing
  wscript.quit err.Number
 End If
 ts.Write strNew
 ts.Close
 wscript.echo "info: file updated successfully!"
End If
 
Set ts = Nothing
Set objINISource = Nothing
Set objFSO = Nothing
Set ts = Nothing

wscript.echo "info: completed."
wscript.echo "info: runtime is " & Round(Timer - t1,2) & " seconds"

[terminaten der codenschnitzel]


I know what you're thinking: My variable names suck and my error handling methods could use some improvement.  I could also stand to cut back on some bad dietary habits, but ultimately, someone has to feed the worms.  You're also probably upset that I didn't do this in PowerShell, which I could have, but I'm too lazy tonight and I have to be up early for work tomorrow.

So, assuming the devcon output file has the right output, and the scripts are run under an administrative context, it works pretty well.  The users can plug the device in and life is good.  If bolted together properly it can be deployed via System Center Configuration Manager as an advertisement, or posted in the Application Catalog for self-install.

Next time I might delve into a scenario for converting VBscript into ASP for interfacing with Active Directory, WMI/WBEM and Configuration Manager via a web browser.

Cheers!

No comments: