This article presents the Windows Update Agent API and how you can use it in Delphi.I made a demo that actually handles the most important parts of the API like searching for updates, downloading and installing these updates on your machine.

You can download the sources (early demo) at the Jedi Svn the links are provided at the end of this article.

If you want to download an executable demo you can do it in the download section.

Background

After I talked to Christian Wimmer, he told me that I could learn programming with COM – the Component Object Model. Therefore I started exploring MSDN  to see if there is something interesting which probably could be useful at a later time. So I found the Windows Update Agent API (WUA) which looked quite interesting to me. Well, after a quick search on the net I could see that there were no Delphi demos available using this API. Most of the demo applications, I’ve found, were made with VBScript.

The MSDN defines WUA as followed:

The Windows Update Agent (WUA) API is a set of COM interfaces that enable system administrators and programmers to access Windows Update and Windows Server Update Services (WSUS). Scripts and programs can be written to examine which updates are currently available for a computer, and then you can install or uninstall updates.

The Demo GUI Part

While I was thinking about how to build a fast GUI for the Demo, I discovered Mike Lischke’s VirtualStringTree which was quite nice to show the updates from the WUA API. Every update has a severity level like: critical, important and so on. They can be found here: Severity Levels To show the different levels to the user, I thought it would look much better using PNG’s for this part so I included Thany’s PngLib which supports PNG images and lists.

Wua Api Demo Main Window
Wua Api Demo Configuration
WuaDebug
Wua Api Demo Debug
WuaLog
Wua Api Demo UpdateLog
WuaHistory
Wua Api Demo Update History
WuaRss
Wua Api Demo Rss

 

 

 

 

Virtual Treeview

As mentioned before I’m using VirtualStringTree (VST) to show information about updates. For those of you who’ve never worked with this component before, I’m going to show you some basic stuff how you can fill the tree with your data and other things which must be done. This is because the tree acts really different from the normal treeview control in Delphi.

Adding Nodes to the Tree

For my demo I store the necessary data in a record. It could be a class but I do prefer records (think about memory usage).

type
  PwuaDownLoadDisplay = ^TwuaDownLoadDisplay;

  TwuaDownLoadDisplay = record
    wuaDownloadName: WideString;
    wuaDownloadDescription: WideString;
    wuaDownloadSeverity: TwuaSeverityProperty;
    wuaDownloadUrl: WideString;
    wuaBetaVersion: WideString;
    wuaKB:   WideString;
    wuaLanguage: WideString;
    wuaUpdateUninstallSteps: WideString;
    wuaUpdateReleaseNotes: WideString;
    wuaUninstallNotes: WideString;
    wuaIsUninstallable: WideString;
    wuaRecommendedCPUSpeed: WideString;
    wuaRecommendedHardDiskSpace: WideString;
    wuaRecommendedMemory: WideString;
    wuaEula: WideString;
    isCheckedNode: WideString;
    NodeIdx: integer;
  end;

The next really important part is to let the tree know the size of our record. I usually do this in the OnCreate event of the form:

wuaTree.NodeDataSize := sizeOf(TwuaDownLoadDisplay);

A normal tree has a routine AddText that you can use to insert a node by passing a text as parameter that is displayed as the caption of the node. VirtualTree isn’t that simple. In return the display of the component is much more smooth and faster. VirtualStringTree contains a GetText event which we have to use to set the captions of the nodes.

procedure TMain.wuaTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  Data: PwuaDownLoadDisplay;
begin
  Data := Sender.GetNodeData(Node);
  if Assigned(Data) then
  begin
    try
      case Column of
        0: CellText := Data.wuaDownloadName;
        1: CellText := Data.wuaDownloadDescription;
        2:
        begin
          if Node.Parent = Sender.RootNode then
            CellText := SeverityStateText[Data.wuaDownloadSeverity]
          else
            CellText := ”;
        end;
        3: CellText  := Data.wuaDownloadUrl;
        4: CellText  := Data.wuaBetaVersion;
        5: CellText  := Data.wuaKB;
        6: CellText  := Data.wuaLanguage;
        7: CellText  := Data.wuaEula;
        8: CellText  := Data.wuaUpdateReleaseNotes;
        9: CellText  := Data.wuaIsUninstallable;
        10: CellText := Data.wuaUninstallNotes;
        11: CellText := Data.wuaUpdateUninstallSteps;
        12: CellText := Data.wuaRecommendedCPUSpeed;
        13: CellText := Data.wuaRecommendedHardDiskSpace;
        14: CellText := Data.wuaRecommendedMemory;
        15: CellText := Data.isCheckedNode;
        16: CellText := IntToStr(Data.NodeIdx);
      end;
    except
      MessageDlg(‘Unhandled Column!’, mtError, [mbOK], 0);
    end;
  end;
end;

Don’t make great calculations here. You will slow down drawing of the nodes otherwise! Furthermore, if the node can’t be retrieved (because the data was corrupted) the application will crash immediately! Also remember to release your memory. VirtualStringTree won’t free it automatically. If you don’t you will get memory leaks. The VST provides an event OnFreeNode which you can free it.Because WideString, String (and some more) won’t be initialized automatically if you use GetMem for such records, you should use the functions Initialize and Finalize. They are not necessary if you stick to New and Dispose.

procedure TMain.wuaTreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  Data: PwuaDownLoadDisplay;
begin
  // free the nodes, so we don’t run into memoryleaks…
  Data := Sender.GetNodeData(node);
  Data^.wuaDownloadName := ”;
end;

Now you can add nodes to the tree like:

var
  Data: PwuaDownLoadDisplay;
  node: PVirtualNode;
begin
      wuaTree.BeginUpdate;
      node := wuaTree.AddChild(nil);
      Data := wuaTree.GetNodeData(Node);
      if not (Assigned(Data)) then
        Exit;
      Data.wuaDownloadName     := upd.Title;
      wuaTree.CheckType[node]  := ctCheckBox;
      wuaTree.CheckState[node] := csUncheckedNormal;
      wuaTree.EndUpdate;

 

That’s all for the VST part. You will find much more inside the demo sources, like a callback event that you can use to filter the data and therefore chose which notes are shown or hidden.

WUA API Part

Well I’ll show you here a little example how to search for the Windows updates, I won’t go into details here because thats really too much. Checkout the Subversion demo repository to see the source / details.

   // Interface: ISearchCompletedCallback
    aSearchCompletedCallback: ISearchCompletedCallback;

{ Callbacks for Windows Update Api }
type
  TUpdSearchCallBack = class(TInterfacedObject, ISearchCompletedCallback)
  public
    function Invoke(const searchJob: ISearchJob;
      const callbackArgs: ISearchCompletedCallbackArgs): HResult; stdcall;
  end;

// implementation of the searchcallback as following

function TUpdSearchCallBack.Invoke(const searchJob: ISearchJob;
  const callbackArgs: ISearchCompletedCallbackArgs): HResult;
var
  MainUnit: TMain;
begin
  MainUnit := TMain(Pointer(integer(variant(SearchJob.AsyncState))));
  try
    { the following line is needed here, to capture EOleSysErrors and write them to the debug tree }
    MainUnit.SrcResult := MainUnit.UpdSearcher.EndSearch(SearchJob);

    MainUnit.MarqueePrg.MarqueeSpeed   := 0;
    MainUnit.MarqueePrg.MarqueeEnabled := False;
    MainUnit.MarqueePrg.Destroy;
    MainUnit.WriteDebugString(lgInformation, ‘Search request’,
      ‘Asynch search request finished’, ‘0′);
    MainUnit.lblupdates.Caption :=
      Format(‘Available updates: %d’, [MainUnit.SrcResult.Updates.Count]);

    if MainUnit.SrcResult.Updates.Count = 0 then
    begin
      MainUnit.btnSearch.Enabled   := True;
      MainUnit.btnDownload.Enabled := False;
      MainUnit.btnInstall.Enabled  := False;
      MainUnit.cbFilter.Enabled    := False;
      MainUnit.btnStop.Enabled     := False;
    end
    else
    begin
      MainUnit.btnSearch.Enabled   := False;
      MainUnit.btnDownload.Enabled := True;
      MainUnit.btnInstall.Enabled  := True;
      MainUnit.cbFilter.Enabled    := True;
      MainUnit.btnStop.Enabled     := False;
    end;

    //Benchmark
    MainUnit.FEndTime  := now;
    MainUnit.lblEnd.Caption := Format(‘End: %s’, [TimeToStr(Mainunit.FEndTime)]);
    MainUnit.FDuration := MainUnit.FStartTime - MainUnit.FEndTime;
    MainUnit.lblDuration.Caption :=
      Format(‘Duration: %s’, [TimeToStr(MainUnit.FDuration)]);

  except
    on E: EOleSysError do
    begin
      MainUnit.WriteDebugString(lgException,
       WUAGlobal.GetWUAText(E.ErrorCode).ErrorType,
       WUAGlobal.GetWUAText(E.ErrorCode).ErrorText, WUAGlobal.GetWUAText(E.ErrorCode).ErrorHex);
      MainUnit.MarqueePrg.MarqueeSpeed   := 0;
      MainUnit.MarqueePrg.MarqueeEnabled := False;
      MainUnit.MarqueePrg.Destroy;

      if Assigned(SearchJob) then
        SearchJob.RequestAbort;
    end;
  end;
  Result := S_OK;
end;

Now we can process the update search and fill the tree with result of the ISearchresult.

procedure TMain.btnSearchClick(Sender: TObject);
var
  Data: PwuaDownLoadDisplay;
  d:    TwuaDownLoadDisplay;
  node: PVirtualNode;
  ISearchIdx, KBIDS, LngIds: integer;
begin
{ MarqueeProgressbar which does not flicker on Vista or XP see Codegear for the known issue
  http://qc.borland.com/wc/qcmain.aspx?d=38178…
  Vista progressbar states are defined as: psNormal, psError, psPaused }

  btnStop.Enabled := True;

  MarqueePrg      := TMarqueeProgressbar.Create(self);
  MarqueePrg.Parent := Main;
  MarqueePrg.Top  := 93;
  MarqueePrg.Left := 241;
  MarqueePrg.Width := 135;
  MarqueePrg.Height := 17;
  MarqueePrg.Visible := True;
  MarqueePrg.MarqueeSpeed := 40;
  MarqueePrg.MarqueeEnabled := True;
  MarqueePrg.State := psNormal;

  UpdIsApplicable := False;
  FStartTime      := Now;
  lblStart.Caption := Format(‘Start: %s’, [TimeToStr(FStartTime)]);

{ create the searchcallback and start async search. We search for software which is not installed
  on the machine except driver… }

  aSearchCompletedCallback   := TUpdSearchCallBack.Create;

  aSearchJob := UpdSearcher.BeginSearch(‘IsInstalled=0  and Type=’‘Software’”,
    aSearchCompletedCallback, variant(integer(Self)));

  WriteDebugString(lgInformation, ‘Search request’, ‘Asynch search request executed’, ‘0′);

  { Wait for the job to complete }

  while not aSearchJob.IsCompleted do
    Application.ProcessMessages;

  aSearchJob.CleanUp;

   //on error these values are invalid
  if Assigned(SrcResult)  then
  begin
    { fill our tree… }
    for ISearchIdx := 0 to SrcResult.Updates.Count - 1 do
    begin
      Upd := SrcResult.Updates.Item[ISearchIdx];
      wuaTree.BeginUpdate;
      node := wuaTree.AddChild(nil);
      Data := wuaTree.GetNodeData(Node);
      if not (Assigned(Data)) then
        Exit;

      Data.wuaDownloadName     := upd.Title;
      wuaTree.CheckType[node]  := ctCheckBox;
      wuaTree.CheckState[node] := csUncheckedNormal;

      Data.wuaDownloadUrl := Upd.SupportUrl;
      Data.wuaBetaVersion := BoolToStr(Upd.IsBeta, True);
      Data.NodeIdx     := ISearchIdx;
      Data.wuaIsUninstallable := BoolToStr(Upd.IsUninstallable, True);
      Data.wuaUninstallNotes := Upd.UninstallationNotes;
      Data.wuaUpdateReleaseNotes := Upd.ReleaseNotes;
      Data.wuaRecommendedCPUSpeed := IntToStr(upd.RecommendedCpuSpeed);
      Data.wuaRecommendedHardDiskSpace := IntToStr(upd.RecommendedHardDiskSpace);
      Data.wuaRecommendedMemory := IntToStr(upd.RecommendedMemory);
      Data.wuaLanguage := ‘no language information found…’;

      if Upd.MsrcSeverity = ‘Critical’ then
        Data.wuaDownloadSeverity := sevCritical
      else
      if Upd.MsrcSeverity = ‘Important’ then
        Data.wuaDownloadSeverity := sevImportant
      else
      if Upd.MsrcSeverity = ‘Moderate’ then
        Data.wuaDownloadSeverity := sevModerate
      else
      if Upd.MsrcSeverity = ” then
        Data.wuaDownloadSeverity := sevEmpty;

      if Upd.EulaText = ” then
        Data.wuaEula := ‘no Eula found…’
      else
        Data.wuaEula := upd.EulaText;

      d.wuaDownloadDescription := Upd.Description;
      AddVSTStructure(wuaTree, node, d);

      for KBIDS := 0 to upd.KBArticleIDs.Count - 1 do
      begin
        Data.wuaKB := upd.KBArticleIDs.Item[KBIDS];
      end;

      for LngIds := 0 to Upd.Languages.Count - 1 do
      begin
        Data.wuaLanguage := Upd.Languages.Item[LngIds];
      end;

      wuaTree.EndUpdate;
    end;
  end
  else
    //show debug tree page on error
    pgc1.ActivePage := ts3;
end;

But before it will work, we need to create some instances:

 UpdSession      := CoUpdateSession.Create;
  UpdSearcher     := CoUpdateSearcher.Create;
  UpdToInstall    := CoUpdateCollection.Create;

Additional Information

I have no clue why Microsoft doesn’t implement the wuerror.h headerfile into the new sdk files anymore so I had to build a separate unit for the demo to capture every error related to WUA, including the error description which are shown in the debug tree when they accure.

Known Bugs

Well I must say that like every other application, the WUA Demo isn’t 100% bug free.

    1. The download process (The overall file size of the downloads is not always reported correctly.)

 

  1. The installation process (When installing updates, the progress bars are not updated when the installation is still in progress. They are updated when all installations are finished. I have no idea why but it seems, that the GUI is frozen while the installation is running. Which isn’t the correct behavior for an asynchronous installation process? The funny thing is that a label which is used for the current name of the installation title  is updated correctly anytime.)

If you find a bug which isn’t listed here or if you find workarounds, please let me know! Furthermore contact me if you want to add more features to the Demo.

I’m not a professional programmer, I’ve tried my best to test the demo in a lot of different situations but I can’t guarantee that you don’t render your OS useless while testing the demo. So use it only for testing and educational purposes! I did not experience seriously problems thought.

Prerequisites

Some Delphi packages are necessary. They are not included and must therefore downloaded and installed (if necessary) separately. Please, also respect the license of these packages.

The sources are included into the Jedi Api Lib which can be downloaded from Sourceforge. Wua Demo sources resists inside of the example folder.