Death of a Hotfix – Fun with KB973705

Ever have one of those days when the help desk calls you early and says EVERYTHING IS DOWN!!

We all know that it’s very rarely that bad, but either way you see your day evaporate.  For me yesterday was one of those times.

The Problem: Perhaps 50% of our machines that received MSKB973705 last night can’t run outlook anymore. a quick look shows that the outlook.exe is smaller, does not have a version number anymore and if you run it from the command line you get “Program too big to fit in memory”. Quickly we declined this update in WSUS to prevent the spread and then it’s on to fixing the problem machines.

 The Process: After some research and a call to Microsoft the recommendation is to remove the patch only from affected machines at least until the developers can have a look at it and determine whats really going wrong here. Sounds simple right?

First we need to determine who has the patch, I found the base of the following vbscript from Hey, Scripting Guy (skip to the end for the whole script)

Set colQuickFixes = objWMIService.ExecQuery _
(“Select * from Win32_QuickFixEngineering where HotFixID = ‘KB973507′”)

This will at least tell us if the patch is installed. If so we need a quick check to see what version is on the outlook as most people appear to have received the patch without issue.

dim filesys, a
    Set filesys = CreateObject(“Scripting.FileSystemObject”)
    a = filesys.GetFileVersion(“C:\Program Files\Microsoft Office\OFFICE11\OUTLOOK.EXE”)

If the patch is installed and the outlook version is bad then we need to uninstall the patch using this script I hoped to just use the .uninstall() method of the Win32_QuickFixEngineering object. Sigh, this is not an option. It is an option for the Win32_Product Class but not the  Win32_QuickFixEngineering Class

After much research, , trolling the registry, google and newsgroups we finally found how a patch like this needs to be uninstalled from the command line. I can’t count the number of times I chased down a solution that looked so easy that I couldn’t believe I missed it earlier, only to find again I was looking at KB973507 and not KB973705 … Dyslexics Of The World Untie

msiexec /package {} /uninstall {} /passive /norestart”

now all we need is the product and package GUIDS, these will of course vary based on the version of Office you have installed and the patch in question).

In order to get the product GUID I found the following script WiLstPrd.vbs that gives us the Product ID, the patch ID I just found by going into Add or Remove Programs, finding the patch and clicking to get the support information.

outlookSupportinfo

Finally this give us  the command that actually removes the patch:

“msiexec /package {90110409-6000-11D3-8CFE-0150048383C9} /uninstall {22A15D5A-3165-4970-86BE-A5D5661E77E1} /passive /norestart”

We also find that following the uninstall of this patch some machines pick it up from the WSUS cache even though we have declined it in the console for now, so we add a “wuauclt -detectnow” to force the clients to check in with WSUS before trying to install the same patch again.

 Of course when we start testing we find that we have a number of different states

Users who have installed and patch and rebooted  – Script works

Users who have installed but not rebooted – – patch uninstall appears to work but after a reboot leaves the client in a bad state requiring that outlook be repaired from the control panel. Because of this we added a section to the script to see if the machine has been rebooted today, if not we prompt to reboot and then run the script again.

Users who have not installed the patches – script runs as expected but does not do anything

Users who have attempted to fix it themselves – isn’t it fun when this happens? we will just have to fix any of these by hand as we find them.

A few more updates to the script to do some logging and report via email on it’s usage and we are ready to deploy. We have a discussion about the pros and cons of deploying via SCCM, log-on script, GPO, user run or help desk run. We found some issues with SCCM not wanting to run the script a second time if the initial reboot is required. We decide that with out help desk assistance if required for the initial push and see what happens.

The Solution:

Have the users run the following script: (keep in mind this was a quick and dirty script, but if you see things that could be better let me know)

Download : Script

Download : User Documentation

See the alternate Script from GregP at the bottom of this post.

Updates:

After seeing the logs for a few hundred users I have made a few change, like requiring that a reboot has happened in the last hour or the script will not run, and notifying the user to call the help desk if they are still having problems and the script things everything is OK.

Hey Microsoft:

Before you start, yes I know about Microsoft Connect, and I use it. I also work with my TAM at our expense to try and argue for product improvements. I suggest everyone does the same.

1) How about some standardization on patch uninstallation? I found 3 different ways for 3 different kinds of patches.

2) How about making the uninstall simple? something like you already do for some things… “C:\WINDOWS\$NtUninstallKB975025$\spuninst”

3) How about adding the Product GUID in the technical details for EVERYTHING that is installed under add remove programs.  And would it kill you if  we could cut and paste from there?

The Script :

‘—– Logging constant : 0 = off, 1 = to screen, 2 = to file, 3 = to screen and file
const conLogType = 2
const conLogPath = “c:\”
const conScriptName = “OutlookFix973705-“
Const conClearLog = 0
Const conDisplayLog = 0
Dim username,intFirstRun
Dim strLogFileName
Username=Getusername

Set objFSO = CreateObject(“Scripting.FileSystemObject”)

logmsg “*************** OutlookFix973705 Started. *****************”

Set WshNetwork = WScript.CreateObject(“WScript.Network”)
logmsg “on Computer Name = ” & WshNetwork.ComputerName
strComputer = “.”
Set objWMIService = GetObject(“winmgmts:” _
& “{impersonationLevel=impersonate}!\\” & strComputer & “\root\cimv2”)

Set colOperatingSystems = objWMIService.ExecQuery (“Select * from Win32_OperatingSystem”)
For Each objOS in colOperatingSystems
    dtmBootup = objOS.LastBootUpTime
    dtmLastBootupTime = WMIDateStringToDate(dtmBootup)
    dtmSystemUptime = DateDiff(“h”, dtmLastBootUpTime, Now)
    logmsg “Last boot time: ” & dtmLastBootupTime
    logmsg “bootup: ” &dtmBootup
    logmsg “Uptime: “& dtmSystemUptime
Next
Function WMIDateStringToDate(dtmBootup)
    WMIDateStringToDate = CDate(Mid(dtmBootup, 5, 2) & “/” & Mid(dtmBootup, 7, 2) & “/” & Left(dtmBootup, 4) & ” ” & Mid (dtmBootup, 9, 2) & “:” & Mid(dtmBootup, 11, 2) & “:” & Mid(dtmBootup, 13, 2))
end function
If dtmLastBootupTime < “14/10/2009” then
 logmsg “reboot required before script runs”
 msgbox “Please reboot and run the script again”
 sendmail
 wscript.quit
end if

Set colQuickFixes = objWMIService.ExecQuery _
(“Select * from Win32_QuickFixEngineering where HotFixID = ‘KB973507′”)

logmsg “WMI Query Complete”

For Each objQuickFix in colQuickFixes
  if objQuickFix.HotFixID = “KB973507” then
    dim filesys, a
    Set filesys = CreateObject(“Scripting.FileSystemObject”)
    a = filesys.GetFileVersion(“C:\Program Files\Microsoft Office\OFFICE11\OUTLOOK.EXE”)
 LOGMSG “OUTLOOK vERSION: ” & A & ” :”
   if a<>”” then
  logmsg “outlook version appears to be good, patch not removed”
   end if
   if a=”” then
      logmsg “uninstall started”
      set objShell = wscript.createObject(“wscript.shell”)
      objShell.Run “msiexec /package {90110409-6000-11D3-8CFE-0150048383C9} /uninstall {22A15D5A-3165-4970-86BE-A5D5661E77E1} /passive /norestart”
      Set objNet = Nothing     �
    end if
 end if
Next
      set objShell = wscript.createObject(“wscript.shell”)
      objShell.Run “wuauclt -detectnow”
      Set objNet = Nothing     �
logmsg “wuaudetect now started”
logmsg “script end”
sendmail

‘———————————————————————————–‘�
‘ Procedure  : LogMsg
‘———————————————————————————–‘

Sub LogMsg (strMsg)

dim objWshShell, objFileSystem
dim intLogFile
dim strTemp

 Select Case conLogType
  Case 0
  Case 1
   Set objWshShell = WScript.CreateObject(“WScript.Shell”)
   wscript.echo Now & ” – ” & strMsg
      Set objWshShell = NOTHING
  Case 2
   Set objFileSystem = CreateObject(“Scripting.FileSystemObject”)
   strTemp = Now()
   strTemp = Day(strTemp) & Month(strTemp) & Year(strTemp)
   strLogFileName = conLogPath & conScriptName & strTemp & UserName & “.log”
   If conClearLog = 1 and intFirstRun = 1 Then
    If objFileSystem.FileExists(strLogFileName) then
     objFileSystem.DeleteFile(strLogFileName)
    End If
    intFirstRun = 0
   End if  �
   Set intLogFile = objFileSystem.OpenTextFile(strLogFileName,8,true)
   intLogFile.WriteLine(Now & ” – ” & strMsg)
   intLogFile.Close
   set intLogFile = NOTHING
   set objFileSystem = NOTHING
  Case 3
   Set objFileSystem = CreateObject(“Scripting.FileSystemObject”)
   strTemp = Now()
   strTemp = Day(strTemp) & Month(strTemp) & Right(Year(strTemp),2)
   strLogFileName = conLogPath & conScriptName & strTemp & username & “.log”
   If conClearLog = 1 and intFirstRun = 1 Then
    If objFileSystem.FileExists(strLogFileName) then
     objFileSystem.DeleteFile(strLogFileName)
    End If
    intFirstRun = 0
   End if
   Set intLogFile = objFileSystem.OpenTextFile(strLogFileName,8,true)
   intLogFile.WriteLine(Now & ” – ” & strMsg)
   intLogFile.Close
   wscript.echo   Now & ” – ” & strMsg
   set intLogFile = NOTHING
   set objFileSystem = NOTHING
 end Select
 �
End Sub
‘———————————————————————————–‘
Function GetUserName

dim objWshShell, objWshSysEnv

 Set objWshShell = CreateObject(“WScript.Shell”)
 Set objWshSysEnv = objWshShell.Environment(“PROCESS”)

 GetUserName = objWshSysEnv(“USERNAME”)
 ‘logmsg “Username: ” &Getusername
 Set objWshShell = Nothing
 Set objWshSysEnv = Nothing

End Function

Sub SendMail
Set objMessage = CreateObject(“CDO.Message”)
objMessage.Subject = “Outlook Fix run for: ” & username & ” ” & now() & ” on ” & WshNetwork.ComputerName
objMessage.From = “outlookfix@WHATEVER.COM
objMessage.To = “WHOEVER@WHATEVER.COM
objMessage.TextBody = “”
strTemp = Day(strTemp) & Month(strTemp) & Right(Year(strTemp),2)
strLogFileName = conLogPath & conScriptName & strTemp & UserName & “.log”
logmsg strlogfilename
objMessage.AddAttachment strlogfilename

‘==This section provides the configuration information for the remote SMTP server.
‘==Normally you will only change the server name or IP.
objMessage.Configuration.Fields.Item _
(“http://schemas.microsoft.com/cdo/configuration/sendusing“) = 2

‘Name or IP of Remote SMTP Server
objMessage.Configuration.Fields.Item _
(“http://schemas.microsoft.com/cdo/configuration/smtpserver“) = “YOUR MAIL SERVER IP HERE “

‘Server port (typically 25)
objMessage.Configuration.Fields.Item _
(“http://schemas.microsoft.com/cdo/configuration/smtpserverport“) = 25

objMessage.Configuration.Fields.Update

‘==End remote SMTP server configuration section==

objMessage.Send
end sub

 Alternate Script:

GregP commented with this option, no VBS. I have not tried it but I like the concept. Thanks Greg for the details.

REM Get patch date.
for /f “skip=4 tokens=1,2,3,4″ %%A in (‘reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\9040110900063D11C8EF10054038389C\Patches\A5D51A225613079468EB5A5D66E1771E /v Installed’) do set PatchDate=%%C

if “%PatchDate%”==”” exitREM Get last boot date.

net statistics workstation > %Temp%\NetStats.txt
for /f “tokens=1,2,3,4,5 delims=/ ” %%A in (%Temp%\NetStats.txt) do if /i “%%A”==”Statistics” (if %%C LSS 10 (set BootM=0%%C) else (set BootM=%%C))
for /f “tokens=1,2,3,4,5 delims=/ ” %%A in (%Temp%\NetStats.txt) do if /i “%%A”==”Statistics” (if %%D LSS 10 (set BootD=0%%D) else (set BootD=%%D))
for /f “tokens=1,2,3,4,5 delims=/ ” %%A in (%Temp%\NetStats.txt) do if /i “%%A”==”Statistics” set BootY=%%E
set BootDate=%BootY%%BootM%%BootD%

REM UnPatch or No UnPatch?
if %PatchDate% LSS %BootDate% msiexec /package {90110409-6000-11D3-8CFE-0150048383C9} /uninstall {22A15D5A-3165-4970-86BE-A5D5661E77E1} /quiet /norestart

REM Cleanup.
for %%V in (PatchDate BootM BootD BootY BootDate) do set %%V=
del %Temp%\NetStats.txt

5 thoughts on “Death of a Hotfix – Fun with KB973705

  1. GregP

    Here is a small batch file that will compare the date of the patch install to the date of the last startup and decide whether or not to run the uninstall. It does not include the time, so it’s only precise to the day. That is, if a person installs the patch, and on the same day reboots and then this batch file runs, it will not uninstall the patch because the last boot date is not greater than the patch install date. In such a case, if you run this batch file daily, it won’t do the uninstall until after the *next* time the user reboots. Anyway, here it is (hoping the lines don’t break)…

    @echo off
    REM Uninstall KB973705 if patch date is less than last restart date.

    REM Get patch date.
    for /f “skip=4 tokens=1,2,3,4” %%A in (‘reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\9040110900063D11C8EF10054038389C\Patches\A5D51A225613079468EB5A5D66E1771E /v Installed’) do set PatchDate=%%C

    REM Get last boot date.
    net statistics workstation > %Temp%\NetStats.txt
    for /f “tokens=1,2,3,4,5 delims=/ ” %%A in (%Temp%\NetStats.txt) do if /i “%%A”==”Statistics” (if %%C LSS 10 (set BootM=0%%C) else (set BootM=%%C))
    for /f “tokens=1,2,3,4,5 delims=/ ” %%A in (%Temp%\NetStats.txt) do if /i “%%A”==”Statistics” (if %%D LSS 10 (set BootD=0%%D) else (set BootD=%%D))
    for /f “tokens=1,2,3,4,5 delims=/ ” %%A in (%Temp%\NetStats.txt) do if /i “%%A”==”Statistics” set BootY=%%E
    set BootDate=%BootY%%BootM%%BootD%

    REM UnPatch or No UnPatch?
    if %PatchDate% LSS %BootDate% msiexec /package {90110409-6000-11D3-8CFE-0150048383C9} /uninstall {22A15D5A-3165-4970-86BE-A5D5661E77E1} /quiet /norestart

    REM Cleanup.
    for %%V in (PatchDate BootM BootD BootY BootDate) do set %%V=
    del %Temp%\NetStats.txt

  2. GregP

    Well, the lines did break and some text was lost. Maybe this can help people reconstruct it:

    1. That paragraph after “REM Get patch date” is really only one line. Remove the line breaks and make sure you have a space between ‘query’ and ‘HKLM’.
    Also, there’s some missing text. Immediately between ‘Patches’ and ‘Installed’ you should have:
    “\A5D51A225613079468EB5A5D66E1771E /v ”
    without the quotes.

    2. There are only five lines after “REM Get last boot date”, which begin with ‘net’, ‘for’, ‘for’, ‘for’, and ‘set’. The three ‘for’ lines are broken. For each of the ‘for’ lines, remove the line breaks between %% and A so that you have %%A

    3. That is one line after “REM UnPatch or No UnPatch?”. Remove the line break so that 8CFE- joins with 0150048383C9.

    Everything else looks okay, no other bad line breaks.

  3. GregP

    And just in case the batch file runs on a system without the patch, add this line as the *second* line following “REM Get patch date” (after the ‘for’ statement):

    if “%PatchDate%”==”” exit

    Even without this the batch file would bomb out anyway without harm, but this is a cleaner exit.

  4. GregP

    Scott, feel free to post the batch file in its proper form and eliminate my comments about how to fix it. Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *