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