Uninstall KBUninstall folders on your computers in a domain
Glenn Turner gracious posted up this script to delete KBUninstall folders that end up taking a great deal of room on computers. If you have applied a service pack that replaces the need for a hotfix you do not need these folders. Keep in mind if you remove a folder from a workstation and later on you need to uninstall a specific patch, if you have removed the KBuninstall folder, you can't then uninstall the update.

"Our script is now in production and we were able to delete around 45GB. This will save us 4TB of tape writes annually.

The script connects to AD, grabs a list of all machines in the Servers OU, then goes through each one, first ensuring that it’s online, then checking that the hostname that the server responds with is the machine you’re trying to target (that’s also good for checking that WMI is working. We then find out the location of the windows directory, enumerate all directories containing the string KBUninstall, then delete them if they are older than the deletionthreshold (we chose 60 days).

Load it up in a script editor that is VBScript aware and shows colours so that you can get a better idea of the comments. Obviously, test it before running it (even though I’ve commented out the line that actually does the deletion. When you do run it, use cscript.exe scriptname.vbs otherwise you’ll get a whole load of popups.

I hope this helps somebody."

To download this see below or go to the codeplex site to download the .vbs file directly - http://kbuninstall.codeplex.com/

 


 

' Script written by Glenn Turner - nzdude (at) hotmail dot com.
' Dedicated to Susan.
' Please feel free to modify and distribute as you see fit, including deleting these lines.
' Sign up for the patch management mailing list because it's awesome:
' http://www.patchmanagement.org/

 

Option Explicit

Const HKEY_LOCAL_MACHINE = &H80000002
Const HKEY_CURRENT_USER = &H80000001
Const REG_SZ = 1
Const REG_EXPAND_SZ = 2
Const REG_BINARY = 3
Const REG_DWORD = 4
Const REG_MULTI_SZ = 7
Const HKLM = 0
Const HKCU = 1
Const ADS_SCOPE_SUBTREE = 2
Const ForReading = 1
Const ForWriting = 2
Const ForAppending = 8

Dim objFSO, objOutputFile, objNetwork, objRoot, objMachineDictionaryItem, objMachineDictionary, objUserDictionaryItem, objUserDictionary
Dim strComputer, vMachineDictionaryCount, vUserDictionaryCount, vCurrentMachineCount
Dim vIPAddress, sLocation, objWMIService, colItems, objItem, HostName
Dim strDefaultDomainNC, eqs, eqs1, DiffString, DomainString, strADSQuery
Dim objADOConn, objADOCommand, objQueryResultSet
Dim strWindowsDirectory, strRemoteWindowsDirectory, objFolder, colSubfolders, objSubFolder, objCurrentSubFolder, arrNames, vTotalSubFolderSize, vAllServersTotalSubFolderSize
Dim DeletionThreshold

Set objFSO = CreateObject("Scripting.FileSystemObject") 'Creates File System Object
Set objNetwork = CreateObject("WScript.Network") 'Creates Network Object

Set objMachineDictionary = CreateObject("Scripting.Dictionary")
Set objUserDictionary = CreateObject("Scripting.Dictionary")

vMachineDictionaryCount = 0
vUserDictionaryCount = 0
vCurrentMachineCount = 0

DeletionThreshold = 60  'This is where we specify the minimum age (days) at which folders can be deleted.

WScript.Echo "Running script with deletion threshold of " & DeletionThreshold & " days"
'This gets all PCs from the PCs Ou in the active directory
WScript.Echo Time & ": Retrieving all computers (in specified OU only) from Active Directory . . ."

'Attempts to get the RootDSE of the Active Directory
Set objRoot = GetObject("ldap://RootDSE/")
strDefaultDomainNC = objRoot.Get("DefaultNamingContext")
'----------------------------------------------------------------------------------

eqs = InStr(1,strDefaultDomainNC,"=")
eqs1 = InStr(1,strDefaultDomainNC,",")

DiffString = (Len(strDefaultDomainNC)-(eqs1-eqs-1))
DomainString = Mid(strDefaultDomainNC, eqs+1, Len(strDefaultDomainNC)-DiffString)

If DomainString = "internal" Then
 DomainString = "companyname"   'This is where we put our domain name in.
End If

'----------------------------------------------------------------------------------

Set objRoot = Nothing

If (IsEmpty(strDefaultDomainNC)) Then
      WScript.Echo("Could not get the Default Naming Context")
      WScript.Quit
End If

Set objADOConn = createObject("ADODB.Connection")

objADOConn.Provider = "ADsDSOObject"
objADoConn.Open "Active Directory Provider"

Set objADOCommand = CreateObject("ADODB.Command")
Set objADOCommand.ActiveConnection = objADOConn

'Sets up a connector to the Active Directory, and runs a query to get all servers.
strADSQuery = "SELECT Name FROM 'LDAP://ou=Servers," & strDefaultDomainNC & "' WHERE objectClass = 'computer'"   ' this is where you specify the OU that you want to grab the members from.
'Run the command, output results to a record Set
objADOCommand.CommandText = strADSQuery
'----------------------------------------------------------------------------------
'Set caching etc
objADOCommand.properties("Timeout")=30 '30 Secs
objADOCommand.properties("Cache Results")=True
objADOCommand.properties("Page Size")=200
objADOCommand.properties("Size Limit")=5000
objADOCommand.properties("searchscope")=ADS_SCOPE_SUBTREE
'----------------------------------------------------------------------------------

Set objQueryResultSet = objADOCommand.Execute

If (objQueryResultSet.EOF) Then
   WScript.Echo("Error searching for machines")
   WScript.Quit
End If

While Not objQueryResultSet.EOF
 objMachineDictionary.Add vMachineDictionaryCount, UCASE(objQueryResultSet.Fields("Name"))
 vMachineDictionaryCount = vMachineDictionaryCount + 1
 objQueryResultSet.MoveNext
Wend

WScript.Echo Time & ": " & vMachineDictionaryCount & " machine accounts retrieved from Active Directory."
Set objADOConn = Nothing

vAllServersTotalSubFolderSize = 0

For Each objMachineDictionaryItem in objMachineDictionary
 strComputer = UCase(objMachineDictionary.Item(objMachineDictionaryItem))
 
 'WScript.Echo strComputer
 If PingMachine(strComputer) = "Online" Then
  'wscript.Echo strComputer & " is online."
  If CheckHostName(strComputer) = True Then
   'WScript.Echo "Hostname matches"
   strWindowsDirectory = ""
   strWindowsDirectory = GetszValue(strComputer, HKLM, "software\microsoft\windows NT\currentversion", "systemroot")
   'WScript.Echo "Windows directory for " & strComputer & " located at: " & strWindowsDirectory
       arrNames = Split(strWindowsDirectory, ":")
   strRemoteWindowsDirectory = "\\" & strComputer & "\" & arrNames(0) & "$" & arrNames(1)
   'WScript.Echo VbCrLf & strComputer & " (" & strWindowsDirectory & ")" &  VbCrLf
   If objFSO.FolderExists(strRemoteWindowsDirectory) Then
    Set objFolder = objFSO.GetFolder(strRemoteWindowsDirectory)
    Set colSubfolders = objFolder.Subfolders
    vTotalSubFolderSize = 0
    For Each objSubFolder in colSubfolders
     If InStr (UCase(objSubfolder.Name), "$NTUNINSTALLKB") Then
      Set objCurrentSubFolder = objFSO.GetFolder(strRemoteWindowsDirectory & "\" & objSubfolder.Name)
      If DateDiff("d",objCurrentSubFolder.DateLastModified,Date()) > DeletionThreshold Then
       'WScript.echo "Folder: " & objCurrentSubFolder.Name & vbTab & "Created on: " & objCurrentSubFolder.DateLastModified &  " (" & DateDiff("d",objCurrentSubFolder.DateLastModified,Date()) & " days old)" & vbTab & "Size: " & Round((objCurrentSubFolder.Size / 1048576),1) & " MB"
       vTotalSubFolderSize = vTotalSubFolderSize + objSubfolder.Size
       'objCurrentSubFolder.Delete  'This line does the actual deletion.  To determine how much data will be saved, keep it commented.
      End If
     End If
    Next
    vAllServersTotalSubFolderSize = vAllServersTotalSubFolderSize + vTotalSubFolderSize
    WScript.Echo strComputer & " " & Round((vTotalSubFolderSize / 1048576),1) & "MB"
   End If
  End If
 End If
Next

WScript.Echo VbCrLf & Round((vAllServersTotalSubFolderSize / 1048576),1) & "MB total on all servers." & VbCrLf & "Script finished."
WScript.Quit 

'==========================================================================
Function PingMachine(strComputer)
'==========================================================================
 
 Dim cPingResults 'collection of instances of Win32_PingStatus Class
 Dim objPingResult  'single instance of Win32_PingStatus Class
 
 Set cPingResults = GetObject("winmgmts:{impersonationLevel=impersonate}//" & _
   "./root/cimv2"). ExecQuery("SELECT * FROM Win32_PingStatus " & _
   "WHERE Address = '" + strComputer + "'")
 
 For Each objPingResult In cPingResults
  If objPingResult.StatusCode = 0 Then
   PingMachine = "Online"
   vIPAddress = objPingResult.ProtocolAddress
  Else
   PingMachine = "Offline"
  End If
 Next

End Function

'==========================================================================
Function CheckHostName(strComputer)
'==========================================================================
 On Error Resume Next
 Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
 Set colItems = objWMIService.ExecQuery("Select * from Win32_NetworkAdapterConfiguration",,48)
 For Each objItem in colItems
  HostName = objItem.DNSHostName
  If Not IsNull(HostName) Then Exit For
 Next
 
 If UCASE(HostName) = UCASE(strComputer) Then
  CheckHostName = True
 End If
 
End Function

'==========================================================================
Function GetszValue(strComputer, HKEY, strKeyPath, strValueName)
'==========================================================================

 On Error Resume Next
 
 Dim objReg
 Dim strValue
 
 Set objReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")
 If HKEY = HKLM Then
  objReg.GetStringValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,strValue
 ElseIf HKEY = HKCU Then
  objReg.GetStringValue HKEY_CURRENT_USER,strKeyPath,strValueName,strValue
 End If
 
 GetszValue = strValue
 
End Function