Monday, September 17, 2012

Windows Scripting Host Sucks on 64-bit Windows

Here's an example of something I've known about for years, but somehow forgot, and at the most inconvenient time: Windows Scripting Host SUCKS on 64-bit Windows.  I'm almost ready to dump VBScript for good and move on with PowerShell after this.  There is a workaround for this, but it's stupid.  It's beyond stupid.  It's pathetic.

The code below looks for the Uninstall key value named "DisplayName" for 7-zip 9.20 on a Windows 7 64-bit computer.  The first key path returns "null".  The second key path returns the appropriate version "9.20.00.0"


[CODE]

Set objShell = CreateObject("Wscript.Shell")

Const HKEY_LOCAL_MACHINE = &H80000002
Const appGUID = "{23170F69-40C1-2701-0920-000001000000}"
Const vName = "DisplayName"

kPath1 = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
kPath2 = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"

wscript.echo RegValue(kPath1 & "\" & appGUID, vName)
wscript.echo RegValue(kPath2 & "\" & appGUID, vName)
 
Function RegValue(key, v)
  Dim objRegistry, strValue
  Set objRegistry = GetObject("winmgmts:{impersonationLevel=impersonate}" & _
    "!\\.\root\default:StdRegProv")
  objRegistry.GetStringValue HKEY_LOCAL_MACHINE, key, v, strValue
  RegValue = strValue
End Function

[/CODE]

The screen capture below shows the Registry key and its values.  Note the path shown in the Status Bar along the bottom.  This is the default location under HKLM\Software\Microsoft.


When you install most 64-bit applications, this is where they will record their Uninstall information.  If you install a 32-bit application however, it puts the information under HKLM\Software\WOW6432Node\Microsoft\...


Note that the Uninstall GUID for 7-Zip 9.20 doesn't exist under the Wow6432Node tree.  It only resides under the default tree.  This can get really messy when you start installing 32-bit applications on 64-bit Windows 7.  And before you think that's easy to avoid, think again.

The problem is that CScript under the C:\Windows\SysWOW64 path only looks under the Wow6432Node tree for anything.  Even when you use the ExpandEnvironmentStrings method of the Shell object, it will expand the variable using what it finds here.

For example, if you execute the following VBScript code on a 64-bit machine...


[CODE]

Set objShell = CreateObject("Wscript.Shell")
wscript.echo objShell.ExpandEnvironmentStrings("%programfiles%")
wscript.echo objShell.ExpandEnvironmentStrings("%programfiles(x86)%")

wscript.echo objShell.RegRead("HKLM\SOFTWARE\WOW6432Node\" & _
  "Microsoft\Windows\CurrentVersion\CommonFilesDir")
wscript.echo objShell.RegRead("HKLM\SOFTWARE\Microsoft\" & _
  "Windows\CurrentVersion\CommonFilesDir")

[/CODE]

Notice the path of CScript.exe I invoke in the output capture below.  Note the impact each has on the output also...
Compare the two Value collections under each Registry key path.  The path is shown in the Status Bar along the bottom of each window...
One more example, and this is where it shows it's 2:00 AM beer goggles ugliness...
Consider the following two Registry keys:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Dave
HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Dave
Both keys have a Value named "DisplayName", but key1 is assigned "Dave123", while the same Value under key2 is assigned "Dave456" (both are type REG_SZ).
[CODE]

Set objShell = CreateObject("Wscript.Shell")
key1 = "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Dave"
key2 = "HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Dave"
wscript.echo objShell.RegRead(key1 & "\DisplayName")
wscript.echo objShell.RegRead(key2 & "\DisplayName")

[/CODE]

If you execute the above code using CScript.exe from both locations on a typical Windows 7 64-bit computer, this is what you'd see...
Conclusion
I promised you a "workaround" at the beginning of this article, and here it is:  On 64-bit Windows 7 systems, to ensure you get accurate results, you absolutely HAVE to invoke the CScript.exe from C:\Windows\System32.  However, if you are are looking for installed Applications you need to invoke BOTH of them.  That's right, both of them.  Why? Because you can't get a complete picture without poking into both "sides" of the Registry of a 64-bit Windows 7 computer.
And I haven't even mentioned crawling through HKCU to find installed apps.  Some of you may be making a funny face right now, thinking "there's no application uninstall keys under HKCU" and then you check and realize that there are.  Most ClickOnce application installations, and pretty much any "per-user" installations for that matter, will hide their Registry stuff under HKCU.  So to really see what's "installed" on a 64-bit client, you need to look under the following places...

  • HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall
  • HKLM\Software\Wow6432Node\Microsoft\ Windows\CurrentVersion\Uninstall
  • HKEY_USERS (crawl every SID tree beneath it)
  • probably other locations that I'm just too lazy to dig up right now

Is this retarded?  Yep.  Could Microsoft remedy this 64-bit issue with a patch for WSH and Wscript.exe/CScript.exe?  Yep.  Will they?  Don't bet on it.  
Note:  Wrapped code lines in the above examples are for formatting only.  The actual script code used for the examples does not have the lines wrapped.

No comments: