Jump to content

Listing Patch History


Recommended Posts

As someone may or may not know, you can look in the registry at HKLM\SOFTWARE\Microsoft\Updates and find a list of patches by KB number and file names spread among all the subkeys for some things for that main link.

So, I thought it might be interesting to try and aggregate the information. You can see an image of the final thing I came up with below:

UPDCHECK.JPG

I say "patch history on Windows XP" because I can't really say this works on anything else at the moment (haven't tested it, so I don't know, but you can always try it). As for Windows 95/98/ME, I could probably make it work there, but I can't see on my copy of Windows ME that there's much there to look at. So I stuck a check for an NT based operating system on this. I spot checked a few of the entries and all seems well.

Hopefully this might be useful to someone.

Highlights from the code for a Delphi programming example standpoint:

1) A rudimentary use of TTreeView.

2) Demonstrates recursion through the registry.

3) Demonstrates the use of TList and sorting.


unit updunit;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, StdCtrls, registry, ExtCtrls;

type
TForm1 = class(TForm)
TreeView1: TTreeView;
UpdBtn: TButton;
SaveBtn: TButton;
SaveDialog1: TSaveDialog;
RadioGroup1: TRadioGroup;
procedure UpdBtnClick(Sender: TObject);
procedure SaveBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

type
file_record = record
package_name: string;
filename: string;
end;
pfile_record = ^file_record;

var
Form1: TForm1;
file_value: TList;
file_list: pfile_record;

implementation

{$R *.DFM}

function os_is_nt: Boolean;
{ returns whether the OS is NT based or not }
var
osvinfo: TOsVersionInfo;
begin
{ get windows version }
osvinfo.dwOSVersionInfoSize := Sizeof(osvinfo);
GetVersionEx(osvinfo);
os_is_nt := (osvinfo.dwPlatformId = VER_PLATFORM_WIN32_NT);
end;

function get_pn(keyname: string): string;
{ parse out name of patch from registry entry }
var
filelist_pos: integer;
i: integer;
begin
filelist_pos := pos('\Filelist', keyname);
i := filelist_pos - 1;
while keyname[i] <> '\' do dec(i);
get_pn := copy(keyname, i+1, filelist_pos - i - 1);
end;

procedure recurse_list(keyname: string);
{ recurse through patch list, identify listed files }
var
appcheck: TRegistry;
mainkeys: TStringList;
mainvalues: TStringList;
count1, count2: integer;
begin
appcheck := TRegistry.Create;
appcheck.rootkey := HKEY_LOCAL_MACHINE;
if appcheck.openkey(keyname, false) then
begin
if pos('Filelist', keyname) > 0 then
begin
mainvalues := TStringList.Create;
appcheck.getvaluenames(mainvalues);
if mainvalues.count > 0 then
begin
New(file_list);
file_list^.package_name := get_pn(keyname);
for count2 := 0 to mainvalues.count - 1 do
if mainvalues.strings[count2] = 'FileName' then
file_list^.filename := UpperCase(appcheck.ReadString('FileName'));
file_value.add(file_list);
end;
mainvalues.Destroy;
end;
mainkeys := TStringList.Create;
appcheck.getkeynames(mainkeys);
for count1 := 0 to mainkeys.count - 1 do
recurse_list(keyname + '\' + mainkeys.strings[count1]);
mainkeys.Destroy;
end;
appcheck.Destroy;
end;

function filename_sort(Item1, Item2: Pointer): integer;
{ -1 if Item 1 < Item 2
0 if Item 1 = Item 2
1 if Item 1 > Item 2 }
var
PItem1: pfile_record;
PItem2: pfile_record;
begin
Result := 0;
PItem1 := Item1;
PItem2 := Item2;
if PItem1^.Filename < PItem2^.Filename then
begin
result := -1; exit;
end;
if PItem1^.Filename > PItem2^.Filename then
begin
result := +1; exit;
end;
if PItem1^.Filename = PItem2^.Filename then
begin
if PItem1^.package_name < PItem2^.package_name then
begin
result := -1; exit;
end;
if PItem1^.package_name > Pitem2^.package_name then
begin
result := +1; exit;
end;
if PItem1^.package_name = Pitem2^.package_name then
begin
result := 0; exit;
end;
end;
end;

function packagename_sort(Item1, Item2: Pointer): integer;
{ -1 if Item 1 < Item 2
0 if Item 1 = Item 2
1 if Item 1 > Item 2 }
var
PItem1: pfile_record;
PItem2: pfile_record;
begin
Result := 0;
PItem1 := Item1;
PItem2 := Item2;
if PItem1^.package_name < PItem2^.package_name then
begin
result := -1; exit;
end;
if PItem1^.package_name > PItem2^.package_name then
begin
result := +1; exit;
end;
if PItem1^.package_name = PItem2^.package_name then
begin
if PItem1^.filename < PItem2^.Filename then
begin
result := -1; exit;
end;
if PItem1^.Filename > Pitem2^.Filename then
begin
result := +1; exit;
end;
if PItem1^.Filename = Pitem2^.Filename then
begin
result := 0; exit;
end;
end;
end;

procedure TForm1.UpdBtnClick(Sender: TObject);
const
updkey: string = 'SOFTWARE\Microsoft\Updates';
var
tnode: TTreeNode;
oldpname: string;
cbvar: string;
i: integer;
begin
UpdBtn.Enabled := false;
SaveBtn.Enabled := false;
RadioGroup1.Enabled := false;
TreeView1.Items.BeginUpdate;
TreeView1.Items.Clear;
file_value := TList.Create;
recurse_list(updkey);
case RadioGroup1.ItemIndex of
0: file_value.sort(packagename_sort);
1: file_value.sort(filename_sort);
end;
i := 0;
file_list := file_value[i];
repeat
OldPName := '';
if RadioGroup1.ItemIndex = 0 then
begin
cbvar := file_list^.package_name;
TNode := TreeView1.Items.Add(nil, cbvar);
repeat
if file_list^.filename <> OldPName then
begin
TreeView1.Items.AddChild(TNode, file_list^.filename);
OldPName := file_list^.filename;
end;
inc(i);
Dispose(file_list);
file_list := file_value[i];
until (cbvar <> file_list^.package_name) or (i = file_value.count - 1);
end;
if RadioGroup1.ItemIndex = 1 then
begin
cbvar := file_list^.filename;
TNode := TreeView1.Items.Add(nil, cbvar);
repeat
if file_list^.package_name <> OldPName then
begin
TreeView1.Items.AddChild(TNode, file_list^.package_name);
OldPName := file_list^.package_name;
end;
inc(i);
Dispose(file_list);
file_list := file_value[i];
until (cbvar <> file_list^.filename) or (i = file_value.count - 1);
end;
until (i = file_value.count - 1);
{ travel all, count nodes on main parents }
for i := 0 to (TreeView1.Items.Count-1) do
begin
If TreeView1.Items[i].Parent = nil then
TreeView1.Items[i].Text := TreeView1.Items[i].Text + ' (' +
IntToStr(TreeView1.Items[i].Count) + ')';
end;
TreeView1.Items.EndUpdate;
{ dispose memory here }
file_value.Free;
UpdBtn.Enabled := true;
SaveBtn.Enabled := true;
RadioGroup1.Enabled := true;
end;


procedure TForm1.SaveBtnClick(Sender: TObject);
begin
if SaveDialog1.Execute then
if FileExists(SaveDialog1.Filename) then
begin
if MessageDlg('Overwrite this file?', mtWarning, [mbYes, mbNo], 0) = mrYes then
TreeView1.SaveToFile(SaveDialog1.FileName);
end
else
TreeView1.SaveToFile(SaveDialog1.FileName);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
if not os_is_nt then
begin
MessageDlg('Your OS is not supported.', mtWarning, [mbOK], 0);
halt(1);
end;
end;

end.

You can download the project (Turbo Delphi 2006) here:

http://rsgp0g.bay.livefilestore.com/y1p-Nx...CK.ZIP?download

Edited by Glenn9999
Link to comment
Share on other sites


Thank for posting your code, it nice to see a different language being used.

I made a VB.net app that list installed updates. I have tested this on Windows 7 x64,

Vista Sp2 x32, and a friend tried it on Xp not sure if it was x32 or x64 version, he

reported it work and listed 104 updates.

I have it list the updates in a Combobox, on Win7 and Vista if there a URL, upon

selection it will open the url address for the update.

Imports System
Imports System.IO
Public Class Form1
Dim Act = Microsoft.VisualBasic.CreateObject("Wscript.Shell")
Dim C1 = 0
'-> On Load Clear The Labels
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Label1.Text = ""
Label2.Text = ""
End Sub
'-> Button Click List Updates
Dim KbSave
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Label1.Text = "Processing Installed Updates"
Dim Wmi = GetObject("winmgmts:\\.\root\CIMV2")
Dim Col = Wmi.ExecQuery("SELECT * FROM Win32_QuickFixEngineering")
For Each Obj In Col
C1 = C1 + 1
KbSave = KbSave & " Hot Fix ID : " & Obj.HotFixID & " : URL : " & Obj.Caption & vbCrLf
ComboBox1.Items.Add(Space(3) & Obj.HotFixID & " : " & Obj.Caption)
Next
Label1.Text = "Completed Querry For Installed Updates"
Label2.Text = "Total Installed Updates : " & C1
Wmi = Nothing
End Sub
'-> After Selection Open URL
Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
If InStr(LCase(ComboBox1.SelectedItem), LCase("http")) Or _
InStr(LCase(ComboBox1.SelectedItem), LCase("www")) Then
Dim Z1 = Split(ComboBox1.SelectedItem, " : ")
Process.Start(Z1(1))
Label1.Text = ""
End If
End Sub
'-> Save The Listed Updates
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
If KbSave = "" Then
MsgBox("Error, the scan for the installed updates, has not been ran" & vbCrLf & _
"Please run the scan for updates, then used the Save Button", 4128, "Error Scan Not Ran")
Else
Dim Desktop = Act.SpecialFolders("Desktop")
Dim KBTxt = Desktop & "\" & My.Computer.Name & "_InstallKB.txt"
Dim sw As StreamWriter = New StreamWriter(KBTxt.ToString)
sw.WriteLine(KbSave)
sw.Close()
Act.Run(Chr(34) & KBTxt.ToString & Chr(34), 1, True)
End If
End Sub
End Class

Source Code

ListUpdates.exe

Link to comment
Share on other sites

Nice unit of Pascal Delphi code, Glenn9999.

There are a number of free utilities available to list the installed updates, of which I think the best is probably Nir Sofer's WinUpdatesList (Description and download link are on: http://www.nirsoft.net/utils/wul.html).

However, I've never seen a utility to list the patches by file before. Now there's a solution to answer questions like: "How did we manage to get file XXX updated?" which would otherwise mean working your way through the lower pane of WinUpdatesList, or trawling through the registry manually.

Thanks.

.

Link to comment
Share on other sites

Thanks for this!

I've tried it on Vista (x86) but unfortunately it fails:

PatchLister.jpg

An attempt to fix. I got it working in my Windows ME VM now (it had the same error), so I took that check out. Can update the source in the first post once I know it's better.

http://rsgp0g.bay.livefilestore.com/y1pMMb...CK.ZIP?download

If it's what I think it is, it's a good lesson that Windows isn't all that standard from version to version. It's definitely a lesson that it's good to have as many versions as possible to test on. As for me, I only have XP and ME to test on...

Hopefully this will fix it.

Link to comment
Share on other sites

As of Vista the list of patches isn't kept in the registry anymore (highly likely that Win 7 and Win 2008 too work just the same) but rather in DataStore.edb

I'm not sure if it's the same .edb files as the Win CE database format or Exchange's, and not sure if there's a OLEDB provider for it or anything (probably not)

Either ways, you'd want to use the WUA API instead, which is documented here. And here's a fairly good example on how to use it.

Link to comment
Share on other sites

As of Vista the list of patches isn't kept in the registry anymore (highly likely that Win 7 and Win 2008 too work just the same) but rather in DataStore.edb

Okay, I would have no way to know - in this case, it probably wouldn't work if it doesn't put things into that registry path. I might experiment with WUA some someday, but I really didn't mean to produce anything *too* usable - just provide a programming example of a few things from something I wanted to play around and do (and people tend to have questions on how to do). As the other poster said, I do find it useful for XP in tracking patches (the update files only write *some* of the patch files there) in some way.

YMMV, but hopefully it will work out if someone does want it for that. But bugs do tend to irritate me, so I thought I'd try to track this one down and fix it.

Edited by Glenn9999
Link to comment
Share on other sites

Here is an example of using WUA API in a vbs script

Save As ListUpdates.vbs

Option Explicit
Dim Act :Set Act = CreateObject("Wscript.Shell")
Dim Fso :Set Fso = CreateObject("Scripting.FileSystemObject")
Dim KB1 :Set KB1 = CreateObject("Microsoft.Update.Session")
Dim KB2 :Set KB2 = KB1.CreateUpdateSearcher
Dim Rpt :Rpt = Act.SpecialFolders("Desktop") & "\ListKB_" & Act.ExpandEnvironmentStrings("%ComputerName%.txt")
Dim Url :Url = "http://support.microsoft.com/?kbid="
Dim Ag1, Ag2, Ag3, Ag4, Col, Obj

For Each Obj in KB2.QueryHistory(1, KB2.GetTotalHistoryCount)
Ag1 = Obj.Title
Ag2 = Obj.Date
If InStr(LCase(Obj.Title),LCase("KB")) Then
If InStr(LCase(Obj.Title),LCase("Definition")) Then
Else
Ag3 = Split(Ag1,"(")
Ag4 = Left(Ag3(1),8)
Ag4 = Replace(Ag4,"KB","")
Col = Col & vbCrLf & _
" Update Title : " & Ag1 & vbCrLf & _
" Install Date : " & Ag2 & vbCrLf & _
" Support URL : " & Url & Ag4 & vbCrLf
End If
End If
Next

If KB2.GetTotalHistoryCount > 1 Then
Set Ag1 = Fso.CreateTextFile(Rpt)
Ag1.WriteLine Col
Ag1.close()
Act.Run(Chr(34) & Rpt & Chr(34)),1,True
End If

Link to comment
Share on other sites

I took the VBS code I posted in the above thread and made it into a VB.net app.

Imports System
Imports System.IO
Public Class Form1
Dim Act = Microsoft.VisualBasic.CreateObject("Wscript.Shell")
Dim Dic = Microsoft.VisualBasic.CreateObject("Scripting.Dictionary")
Dim Cmp = My.Computer.Name
Dim Lne = "-----------------------------------------------------------------------------------------------------------"
Dim Vb = vbCrLf
'-> Listbox1 Open Url
Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
If InStr(LCase(ListBox1.SelectedItem), LCase("http")) Then
Dim A1 = Split(ListBox1.SelectedItem, ": ")
Process.Start(A1(1))
End If
End Sub
'-> Search For Installed Updates
Dim ColKB1, ColKB2, ColKB3
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim KB1 = Microsoft.VisualBasic.CreateObject("Microsoft.Update.Session")
Dim KB2 = KB1.CreateUpdateSearcher
Dim Ag1, Ag2, Ag3, Ag4
Dim T1 = " Update Title : "
Dim T2 = " Install Date : "
Dim T3 = " Support URL : "
Dim Url = "http://support.microsoft.com/?kbid="
ListBox1.Items.Add(Lne)
ListBox2.Items.Add(Lne)
ListBox3.Items.Add(Lne)
Dim KBTotal = KB2.GetTotalHistoryCount
For Each Obj In KB2.QueryHistory(1, KBTotal)
If Not Dic.Exists(Obj.Title) Then
Dic.Add(Obj.Title, Obj.Title)
Ag1 = Obj.Title : Ag2 = Obj.Date
If InStr(LCase(Obj.Title), LCase("Definition")) Then
ListBox2.Items.Add(T1 & Ag1)
ListBox2.Items.Add(T2 & Ag2)
ListBox2.Items.Add(Lne)
ColKB2 = ColKB2 & T1 & Ag1 & vbCrLf & T2 & Ag2 & vbCrLf & Lne & vbCrLf
ElseIf InStr(LCase(Obj.Title), LCase("KB")) Then
Ag3 = Split(Ag1, "(")
Ag4 = Replace(Ag3(1), "KB", "")
Ag4 = Replace(Ag4, ")", "")
ColKB1 = ColKB1 & T1 & Ag1 & vbCrLf & T2 & Ag2 & vbCrLf & _
T3 & Url & Ag4 & vbCrLf & Lne & vbCrLf
ListBox1.Items.Add(T1 & Ag1)
ListBox1.Items.Add(T2 & Ag2)
ListBox1.Items.Add(T3 & Url & Ag4)
ListBox1.Items.Add(Lne)
Else
ListBox3.Items.Add(T1 & Ag1)
ListBox3.Items.Add(T2 & Ag2)
ListBox3.Items.Add(Lne)
ColKB3 = ColKB3 & T1 & Ag1 & vbCrLf & T2 & Ag2 & vbCrLf & Lne & vbCrLf
End If
End If
Next
End Sub
'-> Clear List Button Click
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
ClearAll()
End Sub
'-> Clear All Listboxes And Empty The Dictionary
Private Sub ClearAll()
Try
ListBox1.Items.Clear()
ListBox2.Items.Clear()
ListBox3.Items.Clear()
Dim a, i
a = Dic.Keys
For i = 0 To Dic.Count - 1
Dic.Remove(a(i))
Next
Catch ex As Exception
End Try
End Sub
'-> Save All Update Information
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
If ListBox1.Items.Count > 1 Then
Dim Rpt = Act.SpecialFolders("Desktop") & "\ListKB_" & Cmp & ".txt"
Dim D = Date.Today.Year.ToString & "/" & Date.Today.Month.ToString & "/" & Date.Today.Day.ToString
Dim sw As StreamWriter = New StreamWriter(Rpt.ToString)
Dim Z17 = Space(17)
sw.WriteLine(Lne)
sw.WriteLine(" Computer Name : " & Cmp)
sw.WriteLine(" Date Of Scan : " & D & vbCrLf & Lne & vbCrLf)
sw.WriteLine(Lne & vbCrLf & Z17 & "Windows Updates Security" & vbCrLf & Lne)
sw.WriteLine(ColKB1)
If ListBox2.Items.Count > 1 Then
sw.WriteLine(Lne & vbCrLf & Z17 & "Windows Updates Defender" & vbCrLf & Lne)
sw.WriteLine(ColKB2)
End If
If ListBox3.Items.Count > 1 Then
sw.WriteLine(Lne & vbCrLf & Z17 & "Windows Updates Other" & vbCrLf & Lne)
sw.WriteLine(ColKB3)
End If
sw.Close()
Act.Run(Chr(34) & Rpt.ToString & Chr(34), 1, True)
Else
MsgBox(Space(25) & "No Information Error" & vbCrLf & _
"Press the List Updates Button to have this app start the" & vbCrLf & _
"scan to list all installed updates From Windows Update", 4128)
End If
End Sub
End Class

Here is the Source Code

ListKB_Ver1.exe

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...