Build your XML the LinqToXML style in native code – Part I.

by Iztok Kacin in Coding

A lot of you probably already know SimpleStorage. For those who don’t:

I read a blog entry recently about LinqToXML and while I was exploring the possibilities that it has, I wondered. Can something like this be done with Delphi and native code? Now SimpleStorage already enables you to write XML in a very compact and efficient way, but it still lacks some elegance on the more native XML building approach. Ok there is a question why would you even want to write XML code inside the IDE editor? But sometimes it is viable, maybe just a wrapper XML shell for something that gets dynamically injected into it. Nevertheless I was intrigued if it can be done. Primož Gabrijelčič  has done it and blogged about it:

It was a nice display what fluent interfaces approach can do, but I was not happy completely. I was aiming at code that would get rid of the “Here, Back…” functions to navigate in the document, because you don’t need them. You always build your document in one swoop and maybe access single elements latter. I know why they were used in that approach and that is why I think with fluent interfaces alone it is hard to achieve the wanted result. SimpleStorage uses an approach similar to that, but not to full extent. The second reason was I wanted to implement it using SimpleStorage, because my aim is to evolve it to be a great and all around tool for XML and data manipulation.

The goal I wanted to achieve was that the Delphi code written inside IDE would match the actual XML as closely as possible and with as little boilerplate code as possible. This way you get the benefit of seeing the resulting XML before it is even created. Without further delay let me show you the results.

First the XML we are trying to construct.

<Demografija>
  <Males>
    <Janez>Novak</Janez>
    <Darko>Gazvoda</Darko>
    <Mitja>Dežela</Mitja>
    <Tilen>Medved</Tilen>
  </Males>
  <Females>
    <Person Name="Marija">Gorenjska</Person>
    <Person Name="Tanja">Notranjska</Person>
    <Person Name="Maja">Primorska</Person>
    <Person Name="Tadeja">Pomurje</Person>
    <Person Name="Irma">Posočje</Person>
    <Person Name="Marija">Notranjska</Person>
  </Females> 
</Demografija>

And now the code that does it

procedure TfMain.btnExample2Click(Sender: TObject);
var
  MyStorage: ISimpleStorage;
begin
  MyStorage := CreateStorage('Demografija');
 
  CreateBuilder(MyStorage).Construct(
  [
    AddElement('Males',
    [
      AddElement('Janez', 'Novak'),
      AddElement('Darko', 'Gazvoda'),
      AddElement('Mitja', 'Dežela'),
      AddElement('Tilen', 'Medved')
    ]),
    AddElement('Females',
    [
      AddElement('Person', ['Name', 'Marija'], 'Gorenjska'),
      AddElement('Person', ['Name', 'Tanja'], 'Notranjska'),
      AddElement('Person', ['Name', 'Maja'], 'Primorska'),
      AddElement('Person', ['Name', 'Tadeja'], 'Pomurje'),
      AddElement('Person', ['Name', 'Irma'], 'Posočje'),
      AddElement('Person', ['Name', 'Marija'], 'Notranjska')
    ])
  ]);
end;

And for the second example let me show how to inject the actual dynamically generated XML into the given builder template. The target XML is like this

<Books>
  <English>
    <Science>
      <Mathematics Difficulty="High">5</Mathematics>
      <Physics Difficulty="Low">0</Physics>
      <Computers Difficulty="Medium">8</Computers>
      <Engineering Difficulty="Low">4</Engineering>
      <Electoronics Difficulty="High">8</Electoronics>
      <Biology>
        <Microbiology Difficulty="High">4</Microbiology>
        <Undefined Difficulty="Low">3</Undefined>
      </Biology>
    </Science>
    <Musical>
      <Classic>2</Classic>
      <Etno>2</Etno>
      <Pop>8</Pop>
    </Musical>
  </English>
</Books>

And the code that does it looks like this

procedure TfMain.btnExample1Click(Sender: TObject);
var
  MyStorage: ISimpleStorage;
begin
  MyStorage := CreateStorage('Books');
 
  CreateBuilder(MyStorage).Construct(
  [
    AddElement('English',
    [
      AddElement('Science',
      [
        AddElement('Mathematics', ['Difficulty', 'High'], 5),
        AddElement('Physics', ['Difficulty', 'Low'], 0),
        AddElement('Computers', ['Difficulty', 'Medium'], 8),
        AddElement('Engineering', ['Difficulty', 'Low'], 4),
        AddElement('Electoronics', ['Difficulty', 'High'], 8),
        AddElement('Biology',
        [
          AddElement('Microbiology', ['Difficulty', 'High'], 4),
          AddElement('Undefined', ['Difficulty', 'Low'], 3),
          Add(
            procedure(const Element: IElement)
            begin
              // add few elements here
              Element.Append('InsertedNode').AsInteger := 1;
              Element.Append('InsertedNode').AsInteger := 2;
              Element.Append('InsertedNode').AsInteger := 3;
            end
          )
        ])
      ]),
      AddElement('Musical',
      [
        AddElement('Classic', 2),
        AddElement('Etno', 2),
        AddElement('Pop', 8)
      ])
    ])
  ]);
end;

The result is easily readable dont’ you think? I would really like to hear your opinion on the code. Maybe you have a suggestion or idea, or you simply have a comment about how it all sucks :) The code can already be downloaded from my download page, but be warned, it is still in early development and it will change. It only works in Delphi 2010 (should in 2009) opposed to other parts of SimpleStorage that work in Delphi 2006 and up. Also I use Variants for values for now and that will change. I will replace them with records and implicit operator overloads.

Updated SimpleStorage demos and added IDocument

by Iztok Kacin in Coding

As I promised, I updated the demos. I added two new demos. One showing how to manipulate collection of documents and the other to show how to Encrypt / Decrypt whole documents.

I also made a change in the “Cromis.SimpleStorage.Collection.pas” unit. There is now a new “IDocument” interface which is very simple:

  IDocument = Interface(IInterface)
  ['{0B2F03BD-9929-43A1-A7E8-62B372D2D96C}']
    function GetPath: string;
    function GetData: ISimpleStorage;
    property Data: ISimpleStorage read GetData;
    property Path: string read GetPath;
  end;

Now instead of the collection returning directly ISimpleStorage interface, it returns the IDocument. The interface holds the storage XML and the additional property, the path of the document. This can be important in certain situations. Here I would also like to clarify that for now the collection can only hold XML documents on file system. They cannot be held directly in memory. This is intentional, because in many cases the collections can be huge and there is no way that all those XML documents would fit into memory. It would also be very time consuming to parse and load all XML documents in advance, before even using them.

You can get the new version and demos from the download page.

What is new in SimpleStorage

by Iztok Kacin in Coding

As you probably know by now (or not) SimpleStorage is a set of interfaces to ease the manipulation of XML documents. It is based on OmniXML, a wonderful Delphi XML parser. Simple Storage aims to treat XML as a practical data storage and means of data serialization and transportation. I have written about its usage in a series of articles, some time ago. Here are the articles:

From then, the framework has matured and I use it in a lot of my software. It has proven invaluable in terms of time savings and ease of XML manipulation. After all I have written it because I was tired of doing the same XML tasks over and over again. Recently, I upgraded it with two new important units . They cover two aspects of XML manipulation:

  • Encryption on a document scale
  • Working with collection of documents

I also did an upgrade of the core IElement interface. The goal was easier node attributes manipulation. I will show examples of all three improvements here. The new version of SimpleStorage can be found on my download page as always.

Encryption on a document scale

I recently had the need to encrypt whole XML documents. Since I already have SimpleStorage and XTEA encryption algorithm in my library, this was a no brainer. Whole documents can now be encrypted with some very simple code:

  EncryptedStorage('MyVeryStrongKey').SaveToFile(StorageFromFile(OpenDialog.FileName), SaveDialog.FileName);

And decrypted just as easily.

  EncryptedStorage('MyVeryStrongKey').LoadFromFile(OpenDialog.FileName).SaveToFile(SaveDialog.FileName);

I have intentionally wrote this in one line, because I always want to show how compact and powerfull the whole framework is. It tends to minimize tedious code and make your life easier. In real apps this should probably be two lines for easy redability.

  Storage := StorageFromFile(OpenDialog.FileName);
  EncryptedStorage('MyVeryStrongKey').SaveToFile(Storage , SaveDialog.FileName);

You can also encrypt / decrypt directly but you can look for that yourself. The code is obvious.

Easier node attributes manipulation

As I said, I strive to make the code compact and clear and attribute manipulation was one thing, that bugged me from the beginning of SimpleStorage. Everything else was very compact, but if you wanted to manipulate attribute “Language” of node “Book”, for instance, you had to write something like the following code. The first line is setting “Language” attribute of the “Book” node, by first getting the “Book” node and the second is setting the “Language” directly from “Book” node.

  CurrentNode.Get('Book').Attributes.Ensure('Language').AsString := 'English';
  CurrentNode.Attributes.Ensure('Language').AsString := 'English';

Which is ok, but it stand out from other SimpleStorage code. So I added “shortcuts” to the IElement interface. Now you can do the same like this:

  CurrentNode.EnsureAttr('Book', 'Language').AsString := 'English';
  CurrentNode.EnsureAttr('Language').AsString := 'English';

It saves a lot of characters on the long run :)

Working with collection of documents

Until now, SimpleStorage was great if you wanted to manipulate a single XML document. But what if you have lots of those and you wanted to process all of them. You had to write your own code (which is easy of course, but again repetitive work) and work with each document separately. Well no more. You now have “IDocuments interface that enables you to work over collections of XML documents. I will show this on a simple example. This was a real task, I had in front of me a few days ago. I had a bunch of XML documents looking like this:

<Devices>
  <Device ID="002694350026014400780050BF3F5173" InBound="23632" OutBound="13092" />
  <Device ID="002694350026012900880050BF3F5173" InBound="14728" OutBound="41084" />
  <Device ID="002694359925017200280050BF3F5173" InBound="22116" OutBound="61808" />
  <Device ID="002694350026013800980050BF3F5173" InBound="15140" OutBound="54612" />
  <Device ID="002694359725014400580050BF3F5173" InBound="13460" OutBound="34064" />
  <Device ID="002694359925013100880050BF3F5173" InBound="16332" OutBound="47120" />
  <Device ID="002694359625013600380050BF3F5173" InBound="14860" OutBound="47752" />
  <Device ID="002694359625014900680050BF3F5173" InBound="6120" OutBound="10852" />
  <Device ID="002694359925016600480050BF3F5173" InBound="11724" OutBound="46016" />
  <Device ID="002694350026011900980050BF3F5173" InBound="6404" OutBound="13540" />
  <Device ID="002694359925013600780050BF3F5173" InBound="7616" OutBound="10376" />
  <Device ID="002694359525017200080050BF3F5173" InBound="3060" OutBound="9996" />
  <Device ID="002694359925012400380050BF3F5173" InBound="460" OutBound="34084" />
  <Device ID="002694359725010700280050BF3F5173" InBound="8228" OutBound="35664" />
  <Device ID="002694350026012000780050BF3F5173" InBound="4076" OutBound="26664" />
</Devices>

This is a shortened version, but you get the idea. It is an XML that holds the “InBound” and “OutBound” traffic for mobile devices, over the HTTP protocol. In short, it enables me to see how much traffic my application generates for a single device. The shown XML, is a log for a single month of traffic. And in over two years, quite a few of these files accumulated. It would be easy to make a sum of  “InBound” and “OutBound” traffic and  report that for a single device over the complete time span in a traditional way. But lets see how we can do that with SimpleStorage. First let me show how to get all log entries for a single device. All XML logs were stored in “s:\bandwidth” directory.

procedure TForm1.Button2Click(Sender: TObject);
var
  Documents: IDocuments;
begin
  Documents := CreateCollection('s:\bandwidth', False);
  Documents.Get('//Device[@ID="3FBF5000735108010E1212674B5D036A"]',
    procedure(const Document: string; const Element: IElement; const Data: Pointer)
    begin
      ListBox1.Items.Add(Document + ' - ' + Element.Attributes.Get('InBound').AsString);
    end
  );
end;

Thats it. Simple isn’t it. Try to do the same with classic MS XML and compare the result.  Let me tell you again what I just did in these few lines of code. First I acuired all XML documents in a sinlge directory. Then I enumerated all the documents, but only processed the ones that have a distinct node (based on XPath) in them. Finally I printed some results for the selected nodes. If you dislike the anonymous approach you can also do it with classic callback functions. The interface supports both. the Get function / procedure is overloaded and it returns documents collection or it enumerates nodes over all documents based on XPath.

If you want to filter the collection of documents you have at your disposal you can do it like I have shown in this next example. This is just an example to show you how, it has no real value. In the callback you decide if the document will be included in the result collection or not. Then the code enumerates all documents and prints out a single attribute.

procedure TForm1.Button3Click(Sender: TObject);
var
  Documents: IDocuments;
  Document: ISimpleStorage;
  Element: IElement;
  Subset: IDocuments;
begin
  ListBox1.Clear;
  Randomize;
 
  Documents := CreateCollection('s:\bandwidth', False);
  Subset := Documents.Get('//Device[@ID="3FBF5000735108010E1212674B5D036A"]',
    procedure(const Document: ISimpleStorage; const FileName: string; var Include: Boolean; const Data: Pointer)
    begin
      Include := Random(10) > 5;
    end
  );
 
  for Document in Subset do
  begin
    Element := Document.Get('//Device[@ID="3FBF5000735108010E1212674B5D036A"]');
    ListBox1.Items.Add(Element.GetAttr('InBound').AsString);
  end;
end;

Now finally let me show the real code that helped me sumarize the traffic report. I used a classic callback here for sake of redability.

type
  TDeviceObject = class
    InBound: Int64;
    OutBound: Int64;
  end;
 
procedure TForm1.OnDeviceCallback(const Document: string; const Element: IElement; const Data: Pointer);
var
  DeviceIdx: Integer;
  HashTable: TStringList;
  DeviceObject: TDeviceObject;
begin
  HashTable := TStringList(Data);
  DeviceIdx := HashTable.IndexOf(Element.GetAttr('ID').AsString);
 
  if DeviceIdx = -1 then
    DeviceIdx := HashTable.AddObject(Element.GetAttr('ID').AsString, TDeviceObject.Create);
 
  DeviceObject := TDeviceObject(HashTable.Objects[DeviceIdx]);
  DeviceObject.InBound := DeviceObject.InBound + Element.GetAttr('InBound').AsInteger;
  DeviceObject.OutBound := DeviceObject.OutBound + Element.GetAttr('OutBound').AsInteger;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  HashTable: TStringList;
  Documents: IDocuments;
  OutputData: TStringList;
  DeviceObject: TDeviceObject;
begin
  HashTable := TStringList.Create;
  try
    Documents := CreateCollection('s:\bandwidth', False);
    Documents.Get('//Device', OnDeviceCallback, HashTable);
 
    OutputData := TStringList.Create;
    try
      for I := 0 to HashTable.Count - 1 do
      begin
       DeviceObject := TDeviceObject(HashTable.Objects[I]);
       OutputData.Add(Format('%s,%d,%d', [HashTable[I], DeviceObject.InBound, DeviceObject.OutBound]));
      end;
 
      OutputData.SaveToFile('S:\bandwidth\Bandwidth.txt');
    finally
      OutputData.Free;
    end;
  finally
    HashTable.Free;
  end;
end;

The result is a CSV file ready for excel.

Yahoo messenger and Windows VirtualStore

by Iztok Kacin in OS, Windows

Today I had an interesting challenge in front of me. As I already wrote about in a couple of posts I have a NAS set up for backing up my personal data, as well as my important VMWare machines. I switched my Windows backup client to Cobian last week, because GFI has serious issues with task scheduling. Now I am trying Cobian to see how it will do. So I added the folders, than need to be backup-ed, to the backup plan in Cobian. One of the folders is a Yahoo messenger archive folder for my profile. I use YM for day to day work, because I work from home and it is my link with my coworkers through the day. I store every conversation to the archive, so I have a complete history in case I need some info about app design or anything else, that was decided a while ago and it is not in the mail.

Ok so what did I find out? YM tries to store the profile and archives into the “Program Files (x86)\Yahoo!\Messenger\profiles” folder (I have 64 bit Windows 7). Well Windows VirtualStore kicks in at that moment, redirecting all the data to the “Users\UserName\AppData\Local\VirtualStore\Program Files (x86)\Yahoo!\Messenger\Profile”. I know why MS implemented VirtualStore. User apps have no business writing data to the “Program Files”. But guess what, they do. And a lot of them, even popular apps like YM. And because MS wanted better security and on the same side wanted old apps to continue running, they made the decision about VirtualStore. But I don’t think it was a smart one. Redirecting data to some obscure path, deep into the user profile is not a good idea. Redirecting to a path that has hidden folders in it is even worse idea. People then don’t know what happened with their data and that can lead to data loss or some irrational decision made in panic that also lead to data loss.

Lets take my case for example. Under Windows XP, my archive was written to “Program Files”. After installing Windows 7, I restored to the previous state ,installing YM again. I could see my archived data when I checked if it was there in YM. But from then on all the data was written to the VirtualStore, without me being aware of it. When I restored my backup plan I was missing all the new data recorded after the transition to Windows 7. And I am a developer, so I quickly found that out, when restoring the backup plan. But what about the common user, that has no way of knowing that and resolving such issues. I think a better approach would be:

  1. Let the application crash if it tries to write to “Program Files”
  2. Explicitly tell the application to work in “Windows XP” compatibility
  3. VirtualStore is disabled when applications are run in compatibility mode

Yes, it would be harder for the user in the beginning, but they would quickly learn how to handle legacy apps and application developers would have to fix their applications. And because the message would be clear: “Your app is not working correctly”, a user would be aware of the error instead of having the illusion that all is well.

So how did I solve the problem. I made a NTFS Junction Point. The procedure is simple:

  1. I moved the complete archive from “Program Files” and VirtualStore to my actual profile folder: “Users\UserName\AppData\Local\Yahoo\Profiles
  2. I deleted the old archive locations and I also deleted the profile folder in YM “Program Files” directory
  3. I opened CMD and typed: mklink /D /J “C:\Program Files (x86)\Yahoo!\Messenger\profiles” “c:\Users\UserName\AppData\Local\Yahoo\Profiles”

That is it. Now YM thinks it is writing and reading data from “Program Files” but instead it is doing it from my user profile folder. I made YM do what it should in the first place and worked around VirtualStore. It is an interesting solution that could come handy in many other cases. And for those of you out there wondering: “Did he use the latest YM?” Yes I installed the latest YM before doing anything else. Version 10 still writes to the “Program Files” and I can’t understand why haven’t they fixed that.

I know many will disagree with my view on this topic, but as I see it, this is how things stand right now :)

How to start a GUI process from service, under Windows Vista/7

by Iztok Kacin in Coding

Today I had a interesting challenge in front of me. I had to start a GUI application under Windows Vista or 7 with elevated privileges. And I had to start it from a restricted account. Why? Because the process was meant to start from a USB drive and act as a data synchronization tool. So in order to do some more complicated things, it has to have enough privileges. But it has to be started from a restricted user account, as this will be the account that will synchronize the data via the USB.

After some debating, the decision was, to use a service that will start the GUI application. It goes like this:

  1. The USB is inserted.
  2. The GUI application is auto-started, or the service detects that the USB was inserted.
  3. The GUI application informs the service via IPC that it needs to be elevated and exits immediately.
  4. The service (running under SYSTEM account) starts the process with SYSTEM account token.
  5. The GUI application detects that is was started by the service.
  6. The GUI application has all possible privileges and does its job.

Let me say here, that I am aware of the security risks involved in such procedure. But with proper approach this can be made safe. OK the theory is looking fine. And it works with no problems under Windows 2000/XP. But under Vista/7 it is a different story altogether. Here is the Microsoft paper on the topic:

http://msdn.microsoft.com/en-us/library/ms683502%28VS.85%29.aspx

To make it short, the service running under SYSTEM account is running in an non-interactive window station. In other words, that means, that it cannot interact with the desktop. This was tightened in Windows Vista. You have to specify the “Interactive  service” flag which is discouraged and can even be nullified in the registry (no interactive services). So starting a process from service, makes the process invisible because it is not running in the correct (logged on user) session.

What if we could just get a session for the logged on user, or even the user token and start the new process in the correct session. But wait we can do that, let me show you how. I found a  good example at the code project:

http://www.codeproject.com/KB/vista-security/VistaSessions.aspx

I started there. I ported the code to Delphi, cleaned it and removed the parts that are not needed. Here is the result:

function LaunchAppIntoDifferentSession(const FileName, Params: string; const WaitFor: Boolean): Cardinal;
var
  PI: PROCESS_INFORMATION;
  SI: STARTUPINFO;
  bResult: Boolean;
  LaucherApp: string;
  dwSessionId: DWORD;
  hUserTokenDup, hPToken: THANDLE;
  dwCreationFlags: DWORD;
  CommandLine: string;
  Directory: string;
  tp: TOKEN_PRIVILEGES;
  pEnv: Pointer;
  LD: LUID;
begin
  try
    LaucherApp := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + cLauncherApp;
    dwCreationFlags := NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE;
    CommandLine := Format('%s "%s" "%s"', [LaucherApp,
                                           FileName,
                                           Params]);
    Directory := ExtractFilePath(LaucherApp);
 
    // get the current active session and the token
    dwSessionId := WtsGetActiveConsoleSessionID;
    //WTSQueryUserToken(dwSessionId, &amp;hUserToken);
 
    // initialize startup info
    FillChar(SI, SizeOf(SI), #0);
    SI.cb := SizeOf(STARTUPINFO);
    SI.lpDesktop := PChar('winsta0\Default');
    SI.dwFlags := STARTF_USESHOWWINDOW;
    SI.wShowWindow := SW_SHOWNORMAL;
 
    if OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or
                                           TOKEN_QUERY or
                                           TOKEN_DUPLICATE or
                                           TOKEN_ASSIGN_PRIMARY or
                                           TOKEN_ADJUST_SESSIONID or
                                           TOKEN_READ or
                                           TOKEN_WRITE,
                                           &hPToken) then
    begin
      if LookupPrivilegeValue(nil, SE_DEBUG_NAME, LD) then
      begin
        tp.PrivilegeCount := 1;
        tp.Privileges[0].Luid := LD;
        tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
 
        DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, nil, SecurityIdentification, TokenPrimary, &amp;hUserTokenDup);
        SetTokenInformation(hUserTokenDup, TokenSessionId, @dwSessionId, SizeOf(DWORD));
 
        if CreateEnvironmentBlock(pEnv, hUserTokenDup, True) then
          dwCreationFlags := dwCreationFlags or CREATE_UNICODE_ENVIRONMENT
        else
          pEnv := nil;
 
        // Launch the process in the client's logon session.
        bResult := CreateProcessAsUser(hUserTokenDup,      // client's access token
                                       nil,                // file to execute
                                       PChar(CommandLine), // command line
                                       nil,                // pointer to process SECURITY_ATTRIBUTES
                                       nil,                // pointer to thread SECURITY_ATTRIBUTES
                                       False,              // handles are not inheritable
                                       dwCreationFlags,    // creation flags
                                       pEnv,               // pointer to new environment block
                                       PChar(Directory),   // name of current directory
                                       &si,                // pointer to STARTUPINFO structure
                                       &pi);               // receives information about new process
 
        if not bResult then
        begin
          Result := GetLastError;
          Exit;
        end;
      end
      else
      begin
        Result := GetLastError;
        Exit;
      end;
    end
    else
    begin
      Result := GetLastError;
      Exit;
    end;
 
    if WaitFor then
    begin
      WaitForSingleObject(PI.hProcess, INFINITE);
      GetExitCodeProcess(PI.hProcess, Result);
    end;
  finally
    // close all handles
    CloseHandle(hUserTokenDup);
    CloseHandle(PI.hProcess);
    CloseHandle(PI.hThread);
    CloseHandle(hPToken);
  end;
end;

The code is still not properly cleaned up, especially the error conditions, but it gives you the idea how it is done. In short it does the following:

  1. It gets the session token for the currently logged on user with “WtsGetActiveConsoleSessionID“.
  2. It gets the user token of the current process,  that is the token for the service under SYSTEM account.
  3. It duplicates that user token.
  4. It sets the session for the duplicated token to be the session of the logged on user.
  5. It launches the process with CreateProcessAsUser passing the duplicated, adjusted user token.

Quite simple, but really powerful. What we get is a process with SYSTEM account privileges, but running in the logged on user session. At first I could not get the new process to be shown on the top of the desktop. It was started, I could see it in the taskbar, but I had to manually click on it to restore it to the original (non minimized) state . This is probably the side effect of the “hack “, because of two different sessions. But I had an Idea. What if I first create a “launcher” process, just a simple pascal “program” that just gets some parameters and then starts the real GUI application. This way my process will be started by process that is already running in the correct session. It tried the approach and now it worked perfectly. I get a process with all the priveleges, but it behaves just as any other GUI process. The launcher process is very simple:

program GetMeUpLauncher;
 
uses
  Windows,
  SysUtils,
  ShellAPI;
 
begin
  ShellExecute(0, 'Open', PChar(ParamStr(1)), '/ADMIN', nil, SW_SHOWNORMAL);
end.

The “ADMIN” parameter tells the GUI application that it was started by the service with elevated privileges. I just want to mention another interesting function which is commented in the above example. “WTSQueryUserToken” can get the user token of the currently logged on user. It is a really powerful function. It means we can get the token from a service for the current user and do things in his/her name. This only works if it is called from a process under SYSTEM account. As it is not needed in my example I commented it.

You can get the complete example with the service, launcher and demo client from my downloads page. But you will need my IPC and Jedi JWA in order to compile it. This is why I included precompiled binaries if you want to try it out.

EDIT:

One of the readers pointed out that  it would be better, if the service was doing the actual synchronization. I agree with him. It is always better to use standard and supported techniques than to use hacks or undocumented features. Troubles are always around the next corner in such cases. However, as it usually happenes, we were under severe time constraint and had and already working application, that all of a sudden had to do some more. The easiest and fastest way was to do what we did. But in the future we will probably redesign it to a more standard solution, where the service will do the actual synchronization and the app will only act as the progress monitor.

The “hack” is still worth blogging about through :)