Similar to my last post, here is another UI issue with Windows Vista. Fortunately, this time I have a solution to offer.
Starting with Windows Vista, the "Windows Update" functionality is provided through Control Panel rather than Internet Explorer. In both versions, there is the ability to hide updates. While hidden updates can easily be restored, this feature allows for ignoring unnecessary updates so that they don't continually count towards the number of available updates that are displayed. For me, this includes the 34 "Windows Vista Ultimate Language Packs" that are currently available. Unfortunately, multiple-selection is not enabled in the "View available updates" dialog. There are checkboxes, including a checkbox on the header that can be used to select/unselect all shown updates, but the checkbox selections are used for the "Install" button only. The other options available from the context menu - "View details", "Copy details", and "Hide update" - can currently be applied only one-at-a-time. This means that hiding just the 34 language packs would require no fewer than 68 clicks!
Originally, I assumed that these hidden updates and other preferences would be stored in the Windows registry, or possibly in a file on the file system.
They are in a file, but a database-type file that isn't directly editable: %SystemRoot%\SoftwareDistribution\DataStore\DataStore.edb.
Fortunately, there is a comprehensive Windows API for viewing and editing this information, and it is even easily available to scripting through the Windows Scripting Host and languages such as JScript.
Microsoft's reference is located on MSDN: Windows Update Agent API.
Here is my resulting script that automatically hides all the "Windows Vista Ultimate Language Packs":
var updateSession = WScript.CreateObject("Microsoft.Update.Session");
var updateSearcher = updateSession.CreateUpdateSearcher();
updateSearcher.Online = false;
var searchResult = updateSearcher.Search("CategoryIDs Contains 'a901c1bd-989c-45c6-8da0-8dde8dbb69e0' And IsInstalled=0");
for(var i=0; i<searchResult.Updates.Count; i++){
var update = searchResult.Updates.Item(i);
WScript.echo("Hiding update: " + update.Title);
update.IsHidden = true;
}
If you're not familiar with WSH, this can be simply executed as saving it as a *.js file, then double-clicking.
A better option is to execute the file from a command-line with cscript.
This will cause the output messages to be written to the standard output, instead of popping up a message box that must be acknowledged for each message.
Also, since this script is making administrative changes to the system, it must be executed as an administrator.
"a901c1bd-989c-45c6-8da0-8dde8dbb69e0" is the ICategory.CategoryID for "Windows Vista Ultimate Language Packs".
(This ICategory happens to have a .Type of "Product".)
A similar script can easily be used to perform operations on other sets of updates by simply modifying the search query.
For the above example, the changes can be reverted by updating the script to executed update.IsHidden = false; (instead of true), then re-executing the script.
Alternatively, here the Windows Vista GUI works a little better: By clicking on "Restore hidden updates" from the side panel in Windows Update, the "Restore" button operates on the checkbox selection - allowing all hidden updates to quickly be restored with 2 clicks if desired.
Finally, here is an extended example that doesn't change anything, but displays some of the many details that are available through this API. First, it displays all the updates grouped and nested by category. Note that some updates belong to more than one category. Finally, it displays all available updates in a "flat" view, without using categories.
var updateSession = WScript.CreateObject("Microsoft.Update.Session");
var updateSearcher = updateSession.CreateUpdateSearcher();
updateSearcher.Online = false;
var searchResult = updateSearcher.Search("IsInstalled=1 or IsInstalled=0");
var describeCategory = function(cat, depth){
var pad = new Array(depth + 1).join(" ");
WScript.echo(pad + depth + ": " + cat + ", " + cat.CategoryID + ", " + cat.Name + ", " + cat.Type);
for(var i=0; i<cat.Children.Count; i++){
var child = cat.Children.Item(i);
describeCategory(child, depth + 1);
}
for(var i=0; i<cat.Updates.Count; i++){
var update = cat.Updates.Item(i);
WScript.echo(pad + " " + describeUpdate(update, pad + " "));
}
};
var describeUpdate = function(update, pad){
var u = update;
var np = "\n" + (pad || "") + " ";
return u.Title
+ np + "Type: " + u.Type
//+ np + "Description: " + u.Description
+ np + "IsInstalled: " + u.IsInstalled
+ np + "IsDownloaded: " + u.IsDownloaded
+ np + "IsHidden: " + u.IsHidden
+ np + "AutoSelectOnWebSites: " + u.AutoSelectOnWebSites;
};
for(var i=0; i<searchResult.RootCategories.Count; i++){
var category = searchResult.RootCategories.Item(i);
describeCategory(category, 1);
}
WScript.echo("\n");
for(var i=0; i<searchResult.Updates.Count; i++){
var update = searchResult.Updates.Item(i);
WScript.echo(describeUpdate(update));
}
According to the IUpdateSearcher.Search documentation, the default search criteria is "IsInstalled = 0 and IsHidden = 0".
Unfortunately, there doesn't seem to be a simple option to short-circuit the evaluator to just return all available updates, e.g. "" or "1=1".
So far now, "IsInstalled=1 or IsInstalled=0" results in all updates being displayed.
The only other note concerning the above example is that the "description" line is commented out in the describeUpdate function only because it can be rather verbose, and make the overall output difficult to read.
Feel free to uncomment it to view the details, as well as adding additional lines for all the other properties available from IUpdate.

