Glenn9999 Posted February 14, 2009 Share Posted February 14, 2009 (edited) 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: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;interfaceuses 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 February 14, 2009 by Glenn9999 Link to comment Share on other sites More sharing options...
Yzöwl Posted February 14, 2009 Share Posted February 14, 2009 Thanks for this!I've tried it on Vista (x86) but unfortunately it fails: Link to comment Share on other sites More sharing options...
gunsmokingman Posted February 14, 2009 Share Posted February 14, 2009 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, uponselection it will open the url address for the update.Imports SystemImports System.IOPublic 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 SubEnd ClassSource CodeListUpdates.exe Link to comment Share on other sites More sharing options...
James_A Posted February 14, 2009 Share Posted February 14, 2009 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 More sharing options...
Glenn9999 Posted February 16, 2009 Author Share Posted February 16, 2009 Thanks for this!I've tried it on Vista (x86) but unfortunately it fails: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?downloadIf 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 More sharing options...
CoffeeFiend Posted February 16, 2009 Share Posted February 16, 2009 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.edbI'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 More sharing options...
Glenn9999 Posted February 16, 2009 Author Share Posted February 16, 2009 (edited) 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.edbOkay, 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 February 16, 2009 by Glenn9999 Link to comment Share on other sites More sharing options...
CoffeeFiend Posted February 16, 2009 Share Posted February 16, 2009 if it doesn't put things into that registry pathActually, that registry path doesn't even exist anymore (FYI). Link to comment Share on other sites More sharing options...
gunsmokingman Posted February 16, 2009 Share Posted February 16, 2009 Here is an example of using WUA API in a vbs scriptSave As ListUpdates.vbsOption 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 More sharing options...
James_A Posted February 16, 2009 Share Posted February 16, 2009 ... 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...Works on Windows 2000 too, but I've only carried out limited testing so far. Link to comment Share on other sites More sharing options...
gunsmokingman Posted February 16, 2009 Share Posted February 16, 2009 I took the VBS code I posted in the above thread and made it into a VB.net app.Imports SystemImports System.IOPublic 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 SubEnd ClassHere is the Source CodeListKB_Ver1.exe Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now