The Road to Delphi

Delphi – Free Pascal – Oxygene


5 Comments

Two ways to get the command line of another process using delphi

Note : for a updated version of the code check the Github repo.


Today I will show you how you can retrieve the Command line parameters of an external application from Delphi using the WinApi and the WMI. In order to understand how the Command line parameters are stored and treated by the system, I recommend which you read this article from  Raymond Chen .

The WinApi way

In order to get the command line from an external process using the WinAPI, you must access to the PEB (Process Environment Block) of the application. To get the PEB you can use the NtQueryInformationProcess function

NTSTATUS WINAPI NtQueryInformationProcess(
  __in       HANDLE ProcessHandle,
  __in       PROCESSINFOCLASS ProcessInformationClass,
  __out      PVOID ProcessInformation,
  __in       ULONG ProcessInformationLength,
  __out_opt  PULONG ReturnLength
);

 

function  NtQueryInformationProcess(
  ProcessHandle : THandle;
  ProcessInformationClass : DWORD;
  ProcessInformation : Pointer;
  ProcessInformationLength : ULONG;
  ReturnLength : PULONG
 ): LongInt; stdcall; external 'ntdll.dll';

Passing the ProcessBasicInformation value in the ProcessInformationClass parameter and a buffer to hold the PROCESS_BASIC_INFORMATION returned in the ProcessInformation.

This is the official (MSDN) definition for the PROCESS_BASIC_INFORMATION structure

typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;

And this a more friendly delphi translation of this structure using the NTinterlnals.net site

  PROCESS_BASIC_INFORMATION = packed record
    ExitStatus: DWORD;
    PebBaseAddress: Pointer;
    AffinityMask: DWORD;
    BasePriority: DWORD;
    UniqueProcessId: DWORD;
    InheritedUniquePID:DWORD;
  end;

The key field in this structure is PebBaseAddress, which stores the address of the PEB. from this point now you must digging inside of the PEB structure again

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  BYTE                          Reserved4[104];
  PVOID                         Reserved5[52];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved6[128];
  PVOID                         Reserved7[1];
  ULONG                         SessionId;
} PEB, *PPEB;

and retrieve the value of the ProcessParameters field which is a pointer to a RTL_USER_PROCESS_PARAMETERS structure

typedef struct _RTL_USER_PROCESS_PARAMETERS {
  BYTE           Reserved1[16];
  PVOID          Reserved2[10];
  UNICODE_STRING ImagePathName;
  UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

Finally you can note which the CommandLine field stores the info which are looking for.

The WinAPI Delphi Code

This is the Delphi source which retrieves the Command line parameters from an external application
Notes :

  1. the next code uses hard-coded offsets to read specific locations of the PEB to avoid the declaration the full structures required (feel free to declare these structures and avoid the offsets).
  2. this code only works for 32 bits process because the structure of the PEB differs from 32 to 64 processes.
  3. to gain access to the processes owned by the system the code set the  SeDebugPrivilege token before to use the OpenProcess function.
//Author Rodrigo Ruz V.
//2011-07-20
{$APPTYPE CONSOLE}
uses
  SysUtils,
  Windows;

type
  _UNICODE_STRING = record
    Length: Word;
    MaximumLength: Word;
    Buffer: LPWSTR;
  end;
  UNICODE_STRING = _UNICODE_STRING;

  PROCESS_BASIC_INFORMATION = packed record
    ExitStatus: DWORD;
    PebBaseAddress: Pointer;
    AffinityMask: DWORD;
    BasePriority: DWORD;
    UniqueProcessId: DWORD;
    InheritedUniquePID:DWORD;
  end;

  function  NtQueryInformationProcess(ProcessHandle : THandle; ProcessInformationClass : DWORD; ProcessInformation : Pointer; ProcessInformationLength : ULONG; ReturnLength : PULONG ): LongInt; stdcall; external 'ntdll.dll';

function GetCommandLineFromPid(PID: THandle): string;
const
  STATUS_SUCCESS             = $00000000;
  SE_DEBUG_NAME              = 'SeDebugPrivilege';
  OffsetProcessParametersx32 = $10;//16
  OffsetCommandLinex32       = $40;//64
var
  ProcessHandle        : THandle;
  rtlUserProcAddress   : Pointer;
  CommandLine          : UNICODE_STRING;
  CommandLineContents  : WideString;
  ProcessBasicInfo     : PROCESS_BASIC_INFORMATION;
  ReturnLength         : Cardinal;
  TokenHandle          : THandle;
  lpLuid               : TOKEN_PRIVILEGES;
  OldlpLuid            : TOKEN_PRIVILEGES;
begin
  Result:='';
  if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TokenHandle) then
  begin
    try

      if not LookupPrivilegeValue(nil, SE_DEBUG_NAME, lpLuid.Privileges[0].Luid) then
        RaiseLastOSError
      else
      begin
        lpLuid.PrivilegeCount := 1;
        lpLuid.Privileges[0].Attributes  := SE_PRIVILEGE_ENABLED;
        ReturnLength := 0;
        OldlpLuid    := lpLuid;
        //Set the SeDebugPrivilege privilege
        if not AdjustTokenPrivileges(TokenHandle, False, lpLuid, SizeOf(OldlpLuid), OldlpLuid, ReturnLength) then RaiseLastOSError;
      end;

      ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, PID);
      if ProcessHandle=0 then RaiseLastOSError
      else
      try
        // get the PROCESS_BASIC_INFORMATION to access to the PEB Address
        if (NtQueryInformationProcess(ProcessHandle,0{=>ProcessBasicInformation},@ProcessBasicInfo, sizeof(ProcessBasicInfo), @ReturnLength)=STATUS_SUCCESS) and (ReturnLength=SizeOf(ProcessBasicInfo)) then
        begin
          //get the address of the RTL_USER_PROCESS_PARAMETERS struture
          if not ReadProcessMemory(ProcessHandle, Pointer(Longint(ProcessBasicInfo.PEBBaseAddress) + OffsetProcessParametersx32), @rtlUserProcAddress, sizeof(Pointer), ReturnLength) then
            RaiseLastOSError
          else
          if ReadProcessMemory(ProcessHandle, Pointer(Longint(rtlUserProcAddress) + OffsetCommandLinex32), @CommandLine, sizeof(CommandLine), ReturnLength) then
          begin
            SetLength(CommandLineContents, CommandLine.length);
            //get the CommandLine field
            if ReadProcessMemory(ProcessHandle, CommandLine.Buffer, @CommandLineContents[1], CommandLine.Length, ReturnLength) then
             Result := WideCharLenToString(PWideChar(CommandLineContents), CommandLine.length div 2)
            else
            RaiseLastOSError;
          end;
        end
        else
        RaiseLastOSError;
      finally
        CloseHandle(ProcessHandle);
      end;
    finally
      CloseHandle(TokenHandle);
    end;
  end
  else
  RaiseLastOSError;
end;

begin
 try
   Writeln(GetCommandLineFromPid(5440));
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Readln;
end.

The WMI way

The WMI provides a very reliable and easy way to access the Command line parameters from an external process, all you must to do is use the Win32_Process wmi class and look in the CommandLine property.

The WMI Delphi Code

Notes

  1. The next code can retrieve the command line for 32 and 64 bits processes.
  2. The code uses Late binding to access the WMI, if you want use another way to access the WMI from Delphi (like direct COM access or importing th e Microsoft scripting library) take a look to the Delphi WMI Code creator.
  3. You can change the credentials of the ConnectServer function to access to the command line parameters of a remote machine process.
{$APPTYPE CONSOLE}

uses
  Windows,
  SysUtils,
  ActiveX,
  Variants,
  ComObj;

function  GetCommandLineFromPid(ProcessId:DWORD): string;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
begin;
  Result:='';
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '');
  //if the pid not exist a EOleException exception will be raised with the code $80041002 - Object Not Found
  FWbemObjectSet:= FWMIService.Get(Format('Win32_Process.Handle="%d"',[ProcessId]));
  Result:=FWbemObjectSet.CommandLine;
end;

begin
 try
    CoInitialize(nil);
    try
     Writeln(GetCommandLineFromPid(5452));
    finally
      CoUninitialize;
    end;
 except
    on E:EOleException do
        Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.


7 Comments

Using Delphi and ADSI to enumerate local and remote shared resources

One of the most rich Directory Access Technologies of Microsoft is the Active Directory Service Interfaces (ADSI) which is a set of interfaces designed to access the features of directory services from different network providers, in this case we will use the WinNT Provider to access the shared resources of a local or remote machine.  The aim of this post is show you how you can obtain the information related to the shared resources from a Delphi application.

Getting the interfaces

The common way to access the ADSI Interfaces from Delphi is importing the Active DS type library.

After of import the type library you will got a large unit file with many interfaces , constants and types which we will not need in this case, and only increase the final size of our application. So we can extract the interfaces for access the network shared resources (see the next source that shows the interfaces to the task)

const
  IID_IADsContainer: TGUID = '{001677D0-FD16-11CE-ABC4-02608C9E7553}';
  IID_IADsFileServiceOperations: TGUID = '{A02DED10-31CA-11CF-A98A-00AA006BC149}';
  ADS_SECURE_AUTHENTICATION = $00000001;
type
  IADsCollection = interface(IDispatch)
    ['{72B945E0-253B-11CF-A988-00AA006BC149}']
    function Get__NewEnum: IUnknown; safecall;
    procedure Add(const bstrName: WideString; vItem: OleVariant); safecall;
    procedure Remove(const bstrItemToBeRemoved: WideString); safecall;
    function GetObject(const bstrName: WideString): OleVariant; safecall;
    property _NewEnum: IUnknown read Get__NewEnum;
  end;

  IADs = interface(IDispatch)
    ['{FD8256D0-FD15-11CE-ABC4-02608C9E7553}']
    function Get_Name: WideString; safecall;
    function Get_Class_: WideString; safecall;
    function Get_GUID: WideString; safecall;
    function Get_ADsPath: WideString; safecall;
    function Get_Parent: WideString; safecall;
    function Get_Schema: WideString; safecall;
    procedure GetInfo; safecall;
    procedure SetInfo; safecall;
    function Get(const bstrName: WideString): OleVariant; safecall;
    procedure Put(const bstrName: WideString; vProp: OleVariant); safecall;
    function GetEx(const bstrName: WideString): OleVariant; safecall;
    procedure PutEx(lnControlCode: Integer; const bstrName: WideString; vProp: OleVariant); safecall;
    procedure GetInfoEx(vProperties: OleVariant; lnReserved: Integer); safecall;
    property Name: WideString read Get_Name;
    property Class_: WideString read Get_Class_;
    property GUID: WideString read Get_GUID;
    property ADsPath: WideString read Get_ADsPath;
    property Parent: WideString read Get_Parent;
    property Schema: WideString read Get_Schema;
  end;

  IADsContainer = interface(IDispatch)
    ['{001677D0-FD16-11CE-ABC4-02608C9E7553}']
    function Get_Count: Integer; safecall;
    function Get__NewEnum: IUnknown; safecall;
    function Get_Filter: OleVariant; safecall;
    procedure Set_Filter(pVar: OleVariant); safecall;
    function Get_Hints: OleVariant; safecall;
    procedure Set_Hints(pvFilter: OleVariant); safecall;
    function GetObject(const ClassName: WideString; const RelativeName: WideString): IDispatch; safecall;
    function Create(const ClassName: WideString; const RelativeName: WideString): IDispatch; safecall;
    procedure Delete(const bstrClassName: WideString; const bstrRelativeName: WideString); safecall;
    function CopyHere(const SourceName: WideString; const NewName: WideString): IDispatch; safecall;
    function MoveHere(const SourceName: WideString; const NewName: WideString): IDispatch; safecall;
    property Count: Integer read Get_Count;
    property _NewEnum: IUnknown read Get__NewEnum;
    property Filter: OleVariant read Get_Filter write Set_Filter;
    property Hints: OleVariant read Get_Hints write Set_Hints;
  end;

  IADsServiceOperations = interface(IADs)
    ['{5D7B33F0-31CA-11CF-A98A-00AA006BC149}']
    function Get_Status: Integer; safecall;
    procedure Start; safecall;
    procedure Stop; safecall;
    procedure Pause; safecall;
    procedure Continue; safecall;
    procedure SetPassword(const bstrNewPassword: WideString); safecall;
    property Status: Integer read Get_Status;
  end;

  IADsFileServiceOperations = interface(IADsServiceOperations)
    ['{A02DED10-31CA-11CF-A98A-00AA006BC149}']
    function Sessions: IADsCollection; safecall;
    function Resources: IADsCollection; safecall;
  end;

  IADsResource = interface(IADs)
    ['{34A05B20-4AAB-11CF-AE2C-00AA006EBFB9}']
    function Get_User: WideString; safecall;
    function Get_UserPath: WideString; safecall;
    function Get_Path: WideString; safecall;
    function Get_LockCount: Integer; safecall;
    property User: WideString read Get_User;
    property UserPath: WideString read Get_UserPath;
    property Path: WideString read Get_Path;
    property LockCount: Integer read Get_LockCount;
  end;

  IADsSession = interface(IADs)
    ['{398B7DA0-4AAB-11CF-AE2C-00AA006EBFB9}']
    function Get_User: WideString; safecall;
    function Get_UserPath: WideString; safecall;
    function Get_Computer: WideString; safecall;
    function Get_ComputerPath: WideString; safecall;
    function Get_ConnectTime: Integer; safecall;
    function Get_IdleTime: Integer; safecall;
    property User: WideString read Get_User;
    property UserPath: WideString read Get_UserPath;
    property Computer: WideString read Get_Computer;
    property ComputerPath: WideString read Get_ComputerPath;
    property ConnectTime: Integer read Get_ConnectTime;
    property IdleTime: Integer read Get_IdleTime;
  end;

  IADsFileShare = interface(IADs)
    ['{EB6DCAF0-4B83-11CF-A995-00AA006BC149}']
    function Get_CurrentUserCount: Integer; safecall;
    function Get_Description: WideString; safecall;
    procedure Set_Description(const retval: WideString); safecall;
    function Get_HostComputer: WideString; safecall;
    procedure Set_HostComputer(const retval: WideString); safecall;
    function Get_Path: WideString; safecall;
    procedure Set_Path(const retval: WideString); safecall;
    function Get_MaxUserCount: Integer; safecall;
    procedure Set_MaxUserCount(retval: Integer); safecall;
    property CurrentUserCount: Integer read Get_CurrentUserCount;
    property Description: WideString read Get_Description write Set_Description;
    property HostComputer: WideString read Get_HostComputer write Set_HostComputer;
    property Path: WideString read Get_Path write Set_Path;
    property MaxUserCount: Integer read Get_MaxUserCount write Set_MaxUserCount;
  end;

In addition to the interfaces we need the ADsOpenObject function which allow you binds to an ADSI interface.

check the syntax of this function

HRESULT ADsOpenObject(
  __in   LPCWSTR lpszPathName,
  __in   LPCWSTR lpszUserName,
  __in   LPCWSTR lpszPassword,
  __in   DWORD dwReserved,
  __in   REFIID riid,
  __out  VOID **ppObject
);

The Delphi equivalent can be

function ADsOpenObject(lpszPathName,lpszUserName,lpszPassword : WideString;
dwReserved : DWORD; const riid:TGUID; out ppObject): HResult; safecall; external 'activeds.dll';

or

function ADsOpenObject(lpszPathName,lpszUserName,lpszPassword : WideString;
dwReserved : DWORD; const riid:TGUID; out ppObject): HResult; stdcall; external 'activeds.dll';

As you can see the only difference is the calling convention (safecall vs stdcall), if you want which Delphi check the value of the returned HResult and raises the exception for you then use the safecall calling convention else if you want check manually the HResult returned by the function use stdcall

Listing the Connected Sessions

To list the Open Sessions (Machines connected) to the shared resources we must use the IADsFileServiceOperations interface and the Sessions function which return a collection of the open sessions for the service.

const
   lpszUserName ='';  // set the user name used to establish the connection to the remote machine
   lpszPassword ='';  // set the password used to establish the connection to the remote machine
   lpszComputer ='.'; // the "." is for the local machine, you can set the name of the remote machine
var
  FSO             : IADsFileServiceOperations;
  Sessions        : IADsCollection;
  Session         : IADsSession;
  rgvar           : OleVariant;
  pceltFetched    : Cardinal;
  oEnum           : IEnumvariant;
  dt              : TDateTime;
begin
  //connect to the file service of the loal o remote machine
  OleCheck(ADsOpenObject(Format('WinNT://%s/LanmanServer',[lpszComputer]), lpszUserName, lpszPassword, ADS_SECURE_AUTHENTICATION,IID_IADsFileServiceOperations,FSO));
  //obtain the sessions
  Sessions := FSO.Sessions;
  //Get the enumerator
  oEnum:= IUnknown(Sessions._NewEnum) as IEnumVariant;
  while oEnum.Next(1, rgvar, pceltFetched) = 0 do
  begin
    //cast the current element to IADsSession
    Session:=IUnknown(rgvar) as IADsSession;
    try
      Writeln('Computer        '+Session.Computer);
      dt := Session.ConnectTime / SecsPerDay; // the ConnectTime  property return the value in seconds
      Writeln('Connected Time  '+FormatDateTime('hh:nn:ss',dt));
      dt := Session.IdleTime / SecsPerDay; // the IdleTime property return the value in seconds
      Writeln('Idle Time       '+FormatDateTime('hh:nn:ss',dt));
      Writeln('Name            '+Session.Name);
      Writeln('User            '+Session.User);
      Writeln('');
    finally
      rgvar:=Unassigned; //clear the variant used for hold the values avoiding meory leaks
    end;
  end;
end;

With this simple code you will get the same information returned by the Windows option Computer Management -> System Tools -> Shared folders > Sessions

Listing the shared resources in use (opened)

To List the opened resources like folders and files we must use the IADsFileServiceOperations interface and the Resources function which return a collection of the open resources for the service.

procedure ListSharedResourcesInUse;
const
   lpszUserName ='';
   lpszPassword ='';
   lpszComputer ='.';
var
  FSO             : IADsFileServiceOperations;
  Resources       : IADsCollection;
  Resource        : IADsResource;
  rgvar           : OleVariant;
  pceltFetched    : Cardinal;
  oEnum           : IEnumvariant;
begin
  //connect to the WinNt provider of the local or remote machine and get an instance to the file service
  OleCheck(ADsOpenObject(Format('WinNT://%s/LanmanServer',[lpszComputer]), lpszUserName, lpszPassword, ADS_SECURE_AUTHENTICATION,IID_IADsFileServiceOperations,FSO));
  //Get the opened resources
  Resources := FSO.Resources;
  //get the enumerator
  oEnum:= IUnknown(Resources._NewEnum) as IEnumVariant;
  while oEnum.Next(1, rgvar, pceltFetched) = 0 do
  begin
    //cast the current element of the collection to the IADsResource interface
    Resource:=IUnknown(rgvar) as IADsResource;
    try
      try
       Writeln(Format('Resource %s User %s LockCount %d',[Resource.Path, Resource.User, Resource.LockCount]));
      except
        on E:EOleException  do
        if E.ErrorCode<> HResult($80070002) then  //in some cases this exception (path not found) is raised when you enumerate the opened resources, you can ignore without problems
         raise;
      end;
    finally
      rgvar:=Unassigned; //avoid a memory leak
    end;
  end;
end;

With this code you will get the same information returned by the Windows option Computer Management -> System Tools -> Shared folders > Open files

Listing the Shared Resources

Finally to List the shared resources we must connect to the local or remote machine using the ADsOpenObject function passing a IADsContainer interface which return a collection with the resources.

procedure ListShared;
const
   lpszUserName ='';
   lpszPassword ='';
   lpszComputer ='.';
var
  Shares          : IADsContainer;
  Share           : IADsFileShare;
  rgvar           : OleVariant;
  pceltFetched    : Cardinal;
  oEnum           : IEnumvariant;
begin
  //connect to the network provider and get the collection of shared resources
  OleCheck(ADsOpenObject(Format('WinNT://%s/LanmanServer',[lpszComputer]), lpszUserName, lpszPassword, ADS_SECURE_AUTHENTICATION,IID_IADsContainer,Shares));
  //get the enumerator
  oEnum:= IUnknown(Shares._NewEnum) as IEnumVariant;
  while oEnum.Next(1, rgvar, pceltFetched) = 0 do
  begin
    //cast the current element to IADsFileShare
    Share:=IUnknown(rgvar) as IADsFileShare;
    try
      Writeln('CurrentUserCount '+IntToStr(Share.CurrentUserCount));
      Writeln('Host Computer    '+Share.HostComputer);
      Writeln('Description      '+Share.Description);
      Writeln('Path             '+Share.Path);
      Writeln('Max User Count   '+IntToStr(Share.MaxUserCount));
      Writeln('');
    finally
     rgvar:=Unassigned; //avoid memory leaks
    end;
  end;
end;

The Console application

This is the full source code of a console application to show all the tasks described in this post.

{$APPTYPE CONSOLE}
{.$DEFINE USE_ActiveDs_TLB}

uses
  {$IFDEF USE_ActiveDs_TLB}
  ActiveDs_TLB,
  {$ENDIF}
  Windows,
  ComObj,
  Variants,
  ActiveX,
  SysUtils;

{$IFNDEF USE_ActiveDs_TLB}
const
  IID_IADsContainer: TGUID = '{001677D0-FD16-11CE-ABC4-02608C9E7553}';
  IID_IADsFileServiceOperations: TGUID = '{A02DED10-31CA-11CF-A98A-00AA006BC149}';
  ADS_SECURE_AUTHENTICATION = $00000001;
type
  IADsCollection = interface(IDispatch)
    ['{72B945E0-253B-11CF-A988-00AA006BC149}']
    function Get__NewEnum: IUnknown; safecall;
    procedure Add(const bstrName: WideString; vItem: OleVariant); safecall;
    procedure Remove(const bstrItemToBeRemoved: WideString); safecall;
    function GetObject(const bstrName: WideString): OleVariant; safecall;
    property _NewEnum: IUnknown read Get__NewEnum;
  end;

  IADs = interface(IDispatch)
    ['{FD8256D0-FD15-11CE-ABC4-02608C9E7553}']
    function Get_Name: WideString; safecall;
    function Get_Class_: WideString; safecall;
    function Get_GUID: WideString; safecall;
    function Get_ADsPath: WideString; safecall;
    function Get_Parent: WideString; safecall;
    function Get_Schema: WideString; safecall;
    procedure GetInfo; safecall;
    procedure SetInfo; safecall;
    function Get(const bstrName: WideString): OleVariant; safecall;
    procedure Put(const bstrName: WideString; vProp: OleVariant); safecall;
    function GetEx(const bstrName: WideString): OleVariant; safecall;
    procedure PutEx(lnControlCode: Integer; const bstrName: WideString; vProp: OleVariant); safecall;
    procedure GetInfoEx(vProperties: OleVariant; lnReserved: Integer); safecall;
    property Name: WideString read Get_Name;
    property Class_: WideString read Get_Class_;
    property GUID: WideString read Get_GUID;
    property ADsPath: WideString read Get_ADsPath;
    property Parent: WideString read Get_Parent;
    property Schema: WideString read Get_Schema;
  end;

  IADsContainer = interface(IDispatch)
    ['{001677D0-FD16-11CE-ABC4-02608C9E7553}']
    function Get_Count: Integer; safecall;
    function Get__NewEnum: IUnknown; safecall;
    function Get_Filter: OleVariant; safecall;
    procedure Set_Filter(pVar: OleVariant); safecall;
    function Get_Hints: OleVariant; safecall;
    procedure Set_Hints(pvFilter: OleVariant); safecall;
    function GetObject(const ClassName: WideString; const RelativeName: WideString): IDispatch; safecall;
    function Create(const ClassName: WideString; const RelativeName: WideString): IDispatch; safecall;
    procedure Delete(const bstrClassName: WideString; const bstrRelativeName: WideString); safecall;
    function CopyHere(const SourceName: WideString; const NewName: WideString): IDispatch; safecall;
    function MoveHere(const SourceName: WideString; const NewName: WideString): IDispatch; safecall;
    property Count: Integer read Get_Count;
    property _NewEnum: IUnknown read Get__NewEnum;
    property Filter: OleVariant read Get_Filter write Set_Filter;
    property Hints: OleVariant read Get_Hints write Set_Hints;
  end;

  IADsServiceOperations = interface(IADs)
    ['{5D7B33F0-31CA-11CF-A98A-00AA006BC149}']
    function Get_Status: Integer; safecall;
    procedure Start; safecall;
    procedure Stop; safecall;
    procedure Pause; safecall;
    procedure Continue; safecall;
    procedure SetPassword(const bstrNewPassword: WideString); safecall;
    property Status: Integer read Get_Status;
  end;

  IADsFileServiceOperations = interface(IADsServiceOperations)
    ['{A02DED10-31CA-11CF-A98A-00AA006BC149}']
    function Sessions: IADsCollection; safecall;
    function Resources: IADsCollection; safecall;
  end;

  IADsResource = interface(IADs)
    ['{34A05B20-4AAB-11CF-AE2C-00AA006EBFB9}']
    function Get_User: WideString; safecall;
    function Get_UserPath: WideString; safecall;
    function Get_Path: WideString; safecall;
    function Get_LockCount: Integer; safecall;
    property User: WideString read Get_User;
    property UserPath: WideString read Get_UserPath;
    property Path: WideString read Get_Path;
    property LockCount: Integer read Get_LockCount;
  end;

  IADsSession = interface(IADs)
    ['{398B7DA0-4AAB-11CF-AE2C-00AA006EBFB9}']
    function Get_User: WideString; safecall;
    function Get_UserPath: WideString; safecall;
    function Get_Computer: WideString; safecall;
    function Get_ComputerPath: WideString; safecall;
    function Get_ConnectTime: Integer; safecall;
    function Get_IdleTime: Integer; safecall;
    property User: WideString read Get_User;
    property UserPath: WideString read Get_UserPath;
    property Computer: WideString read Get_Computer;
    property ComputerPath: WideString read Get_ComputerPath;
    property ConnectTime: Integer read Get_ConnectTime;
    property IdleTime: Integer read Get_IdleTime;
  end;

  IADsFileShare = interface(IADs)
    ['{EB6DCAF0-4B83-11CF-A995-00AA006BC149}']
    function Get_CurrentUserCount: Integer; safecall;
    function Get_Description: WideString; safecall;
    procedure Set_Description(const retval: WideString); safecall;
    function Get_HostComputer: WideString; safecall;
    procedure Set_HostComputer(const retval: WideString); safecall;
    function Get_Path: WideString; safecall;
    procedure Set_Path(const retval: WideString); safecall;
    function Get_MaxUserCount: Integer; safecall;
    procedure Set_MaxUserCount(retval: Integer); safecall;
    property CurrentUserCount: Integer read Get_CurrentUserCount;
    property Description: WideString read Get_Description write Set_Description;
    property HostComputer: WideString read Get_HostComputer write Set_HostComputer;
    property Path: WideString read Get_Path write Set_Path;
    property MaxUserCount: Integer read Get_MaxUserCount write Set_MaxUserCount;
  end;
{$ENDIF}

function ADsOpenObject(lpszPathName,lpszUserName,lpszPassword : WideString;dwReserved : DWORD; const riid:TGUID; out ppObject): HResult; stdcall; external 'activeds.dll';

procedure ListConnectedSessions;
const
   lpszUserName ='';
   lpszPassword ='';
   lpszComputer ='.';
var
  FSO             : IADsFileServiceOperations;
  Sessions        : IADsCollection;
  Session         : IADsSession;
  rgvar           : OleVariant;
  pceltFetched    : Cardinal;
  oEnum           : IEnumvariant;
  dt              : TDateTime;
begin
  OleCheck(ADsOpenObject(Format('WinNT://%s/LanmanServer',[lpszComputer]), lpszUserName, lpszPassword, ADS_SECURE_AUTHENTICATION,IID_IADsFileServiceOperations,FSO));
  Sessions := FSO.Sessions;
  oEnum:= IUnknown(Sessions._NewEnum) as IEnumVariant;
  while oEnum.Next(1, rgvar, pceltFetched) = 0 do
  begin
    Session:=IUnknown(rgvar) as IADsSession;
    try
      Writeln('Computer        '+Session.Computer);
      dt := Session.ConnectTime / SecsPerDay;
      Writeln('Connected Time  '+FormatDateTime('hh:nn:ss',dt));
      dt := Session.IdleTime / SecsPerDay;
      Writeln('Idle Time       '+FormatDateTime('hh:nn:ss',dt));
      Writeln('Name            '+Session.Name);
      Writeln('User            '+Session.User);
      Writeln('');
    finally
      rgvar:=Unassigned;
    end;
  end;
end;

procedure ListSharedResourcesInUse;
const
   lpszUserName ='';
   lpszPassword ='';
   lpszComputer ='.';
var
  FSO             : IADsFileServiceOperations;
  Resources       : IADsCollection;
  Resource        : IADsResource;
  rgvar           : OleVariant;
  pceltFetched    : Cardinal;
  oEnum           : IEnumvariant;
begin
  OleCheck(ADsOpenObject(Format('WinNT://%s/LanmanServer',[lpszComputer]), lpszUserName, lpszPassword, ADS_SECURE_AUTHENTICATION,IID_IADsFileServiceOperations,FSO));
  Resources := FSO.Resources;
  oEnum:= IUnknown(Resources._NewEnum) as IEnumVariant;
  while oEnum.Next(1, rgvar, pceltFetched) = 0 do
  begin
    Resource:=IUnknown(rgvar) as IADsResource;
    try
      try
       Writeln(Format('Resource %s User %s LockCount %d',[Resource.Path, Resource.User, Resource.LockCount]));
      except
        on E:EOleException  do
        if E.ErrorCode<> HResult($80070002) then  //ignore path not found exception
         raise;
      end;
    finally
      rgvar:=Unassigned;
    end;

  end;
end;

procedure ListShared;
const
   lpszUserName ='';
   lpszPassword ='';
   lpszComputer ='.';
var
  Shares          : IADsContainer;
  Share           : IADsFileShare;
  rgvar           : OleVariant;
  pceltFetched    : Cardinal;
  oEnum           : IEnumvariant;
begin
  OleCheck(ADsOpenObject(Format('WinNT://%s/LanmanServer',[lpszComputer]), lpszUserName, lpszPassword, ADS_SECURE_AUTHENTICATION,IID_IADsContainer,Shares));
  oEnum:= IUnknown(Shares._NewEnum) as IEnumVariant;
  while oEnum.Next(1, rgvar, pceltFetched) = 0 do
  begin
    Share:=IUnknown(rgvar) as IADsFileShare;
    try
      Writeln('CurrentUserCount '+IntToStr(Share.CurrentUserCount));
      Writeln('Host Computer    '+Share.HostComputer);
      Writeln('Description      '+Share.Description);
      Writeln('Path             '+Share.Path);
      Writeln('Max User Count   '+IntToStr(Share.MaxUserCount));
      Writeln('');
    finally
     rgvar:=Unassigned;
    end;
  end;
end;

begin
 try
    CoInitialize(nil);
    try
      ListSharedResourcesInUse;
      ListConnectedSessions;
      ListShared;
    finally
      CoUninitialize;
    end;
 except
    on E:EOleException  do
        Writeln(Format('EOleException %s Code %x',[E.Message,E.ErrorCode]));
    on E:EOleSysError  do
        Writeln(Format('EOleSysError  %s Code %x',[E.Message,E.ErrorCode]));
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.


6 Comments

Using the Google Safe Browsing API from Delphi

The Google Safe Browsing API is a service that enables applications to check URLs against the Google’s lists of suspected phishing and malware pages. Exist two types of APIs for using the Safe Browsing service, Safe Browsing API v2 and Safe Browsing Lookup API in this article I will show how use the Safe Browsing Lookup API from a Delphi application.

The Safe Browsing Lookup API is designed to provide a simple interface through HTTP GET or POST request and get the state of the URL(s) directly from the server.

Like most of the services provided by Google you need to request an API key. In order to obtain an API key you must log in with your existing Google account and sign up for the API at http://www.google.com/safebrowsing/key_signup.html

Using the GET Method

The Get method  allow to the client only lookup one URL per request. To use the GET method you must make a request to this URL

https://sb-ssl.google.com/safebrowsing/api/lookup?client=CLIENT&apikey=APIKEY&appver=APPVER&pver=PVER&url=URL

Parameters

  • The client parameter indicates the type of client, it could be any name of the client’s choice.
  • The apikey parameter indicates the API key.
  • The appver parameter indicates the version of the client.
  • The pver parameter indicates the protocol version that the client supports. Currently this should be “3.0”. The format is “major.minor”. If we update the protocol, we will make sure that minor revisions are always compatible; however major revision will be incompatible and the server MAY NOT be able to cope with an older protocol.
  • The url parameter indicates the url the client wants to lookup. It must be a valid URL (non ASCII characters must be in UTF-8) and needs to be encoded properly to avoid confusion. For example, if the url contains ‘&’, it could be interpreted as the separator of the CGI parameters. We require the API users to use the percent encoding for the set of “reserved characters”, which is defined in RFC 3986 . A summary of the percent encoding can be found here.

Check this Sample Url

https://sb-ssl.google.com/safebrowsing/api/lookup?client=mydemoapp&<strong>apikey</strong>=1234567890&appver=1.0.1&pver=3.0&url=http%3A%2F%2Fwww.google.com%2F

In this case the values passed are

client = mydemoapp
apikey = 1234567890
appver = 1.0.1
pver   = 3.0
url    = http://www.google.com

Response

The service returns the following HTTP response codes for the GET method

  • 200: The queried URL is either phishing, malware or both, see the response body for the specific type.
  • 204: The requested URL is legitimate, no response body returned.
  • 400: Bad Request — The HTTP request was not correctly formed.
  • 401: Not Authorized — The apikey is not authorized
  • 503: Service Unavailable .

Additionally  the server will include the actual type of URL in the response body when the queried URL matches either the phishing or malware lists, so the body will contain one of these values
“phishing” | “malware” | “phishing,malware"

Delphi Code for the GET Request

The next source uses the Wininet functions to make the GET request, feel free to use another components like Indy or synapse to accomplish this task.

{$APPTYPE CONSOLE}
uses
  Classes,
  Windows,
  WinInet,
  SysUtils;
const
  sUserAgent = 'Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101';
  //¡¡¡¡¡¡¡¡¡¡Please be nice and use your own API key, get a key from here http://code.google.com/apis/safebrowsing/key_signup.html ¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡
  sApiKey    = 'ABQIAAAAzY4CKjsBFYV4Rxx0ZQaKlxQL2a1oqOk9I7UVXAZVtWa6uSA2XA';
  sServer    = 'sb-ssl.google.com';
  sGetSafeBrowsing   = '/safebrowsing/api/lookup?client=delphi&apikey=%s&appver=1.5.2&pver=3.0&url=%s';

//this function translate a WinInet Error Code to a description of the error.
function GetWinInetError(ErrorCode:Cardinal): string;
const
   winetdll = 'wininet.dll';
var
  Len: Integer;
  Buffer: PChar;
begin
  Len := FormatMessage(
  FORMAT_MESSAGE_FROM_HMODULE or FORMAT_MESSAGE_FROM_SYSTEM or
  FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_IGNORE_INSERTS or  FORMAT_MESSAGE_ARGUMENT_ARRAY,
  Pointer(GetModuleHandle(winetdll)), ErrorCode, 0, @Buffer, SizeOf(Buffer), nil);
  try
    while (Len > 0) and {$IFDEF UNICODE}(CharInSet(Buffer[Len - 1], [#0..#32, '.'])) {$ELSE}(Buffer[Len - 1] in [#0..#32, '.']) {$ENDIF} do Dec(Len);
    SetString(Result, Buffer, Len);
  finally
    LocalFree(HLOCAL(Buffer));
  end;
end;

//make a GET request using the WinInet functions
function Https_Get(const ServerName,Resource : string;Var Response:AnsiString): Integer;
const
  BufferSize=1024*64;
var
  hInet    : HINTERNET;
  hConnect : HINTERNET;
  hRequest : HINTERNET;
  ErrorCode : Integer;
  lpvBuffer : PAnsiChar;
  lpdwBufferLength: DWORD;
  lpdwReserved    : DWORD;
  dwBytesRead     : DWORD;
  lpdwNumberOfBytesAvailable: DWORD;
begin
  Result  :=0;
  Response:='';
  hInet := InternetOpen(PChar(sUserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

  if hInet=nil then
  begin
    ErrorCode:=GetLastError;
    raise Exception.Create(Format('InternetOpen Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
  end;

  try
    hConnect := InternetConnect(hInet, PChar(ServerName), INTERNET_DEFAULT_HTTPS_PORT, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);
    if hConnect=nil then
    begin
      ErrorCode:=GetLastError;
      raise Exception.Create(Format('InternetConnect Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
    end;

    try
      //make the request
      hRequest := HttpOpenRequest(hConnect, 'GET', PChar(Resource), HTTP_VERSION, '', nil, INTERNET_FLAG_SECURE, 0);
      if hRequest=nil then
      begin
        ErrorCode:=GetLastError;
        raise Exception.Create(Format('HttpOpenRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
      end;

      try
        //send the GET request
        if not HttpSendRequest(hRequest, nil, 0, nil, 0) then
        begin
          ErrorCode:=GetLastError;
          raise Exception.Create(Format('HttpSendRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
        end;

          lpdwBufferLength:=SizeOf(Result);
          lpdwReserved    :=0;
          //get the status code
          if not HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE or HTTP_QUERY_FLAG_NUMBER, @Result, lpdwBufferLength, lpdwReserved) then
          begin
            ErrorCode:=GetLastError;
            raise Exception.Create(Format('HttpQueryInfo Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
          end;

         if Result=200 then //read the body response in case which the status code is 200
          if InternetQueryDataAvailable(hRequest, lpdwNumberOfBytesAvailable, 0, 0) then
          begin
            GetMem(lpvBuffer,lpdwBufferLength);
            try
              SetLength(Response,lpdwNumberOfBytesAvailable);
              InternetReadFile(hRequest, @Response[1], lpdwNumberOfBytesAvailable, dwBytesRead);
            finally
              FreeMem(lpvBuffer);
            end;
          end
          else
          begin
            ErrorCode:=GetLastError;
            raise Exception.Create(Format('InternetQueryDataAvailable Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
          end;

      finally
        InternetCloseHandle(hRequest);
      end;
    finally
      InternetCloseHandle(hConnect);
    end;
  finally
    InternetCloseHandle(hInet);
  end;
end;

//encode a Url
function URLEncode(const Url: string): string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to Length(Url) do
  begin
    case Url[i] of
      'A'..'Z', 'a'..'z', '0'..'9', '-', '_', '.':
        Result := Result + Url[i];
    else
        Result := Result + '%' + IntToHex(Ord(Url[i]), 2);
    end;
  end;
end;

//Send The GET request and process the returned body
Procedure TestGet(const AUrl : string);
var
 Response     : AnsiString;
 ResponseCode : Integer;
begin
   ResponseCode:=Https_Get(sServer,Format(sGetSafeBrowsing,[sApiKey,URLEncode(AUrl)]), Response);
   case ResponseCode of
     200: Writeln(Format('The queried URL (%s) is %s',[AUrl,Response]));
     204: Writeln(Format('The queried URL (%s) is %s',[AUrl,'legitimate']));
     400: Writeln('Bad Request — The HTTP request was not correctly formed.');
     401: Writeln('Not Authorized — The apikey is not authorized');
     503: Writeln('Service Unavailable — The server cannot handle the request.');
   else
         Writeln('Unknow response');
   end;
end;

begin
  try
     //Now check some urls.
     TestGet('http://malware.testing.google.test/testing/malware/');
     TestGet('orgsite.info');
     TestGet('http://www.google.com');
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

This will return

The queried URL (http://malware.testing.google.test/testing/malware/) is malware
The queried URL (orgsite.info) is malware
The queried URL (http://www.google.com) is legitimate

Using the POST Method

The post request is more powerful because the client can also look up a set of URLs (up to 500) through HTTP POST request. To use the POST method you must make a request to this URL

https://sb-ssl.google.com/safebrowsing/api/lookup?client=CLIENT&apikey=APIKEY&appver=APPVER&pver=PVER

Parameters

  • The client parameter indicates the type of client, it could be any name of the client’s choice.
  • The apikey parameter indicates the API key.
  • The appver parameter indicates the version of the client.
  • The pver parameter indicates the protocol version that the client supports.

Check this Sample Url

https://sb-ssl.google.com/safebrowsing/api/lookup?client=mydemoapp&<strong>apikey</strong>=1234567890&appver=1.0.1&pver=3.0

Request Body

The client specifies the queried URLs in the POST request body using the following format:
POST_REQ_BODY = NUM LF URL (LF URL)*
NUM = (DIGIT)+
URL = url string following the RFC 1738

The request’s body contains several lines separated by LF. The first line is a number indicating how many URLs are included in the body. The next several lines are URLs to be looked up. Each line contains one URL and the client must specify at least one URL in the body.

check this sample

2
http://www.google.com/
http://malware.testing.google.test/testing/malware/

Response

The server generates the following HTTP response codes for the POST request:

  • 200: AT LEAST ONE of the queried URLs are matched in either the phishing or malware lists, the actual results are returned through the response body
  • 204: NONE of the queried URLs matched the phishing or malware lists, no response body returned
  • 400: Bad Request — The HTTP request was not correctly formed
  • 401: Not Authorized.
  • 503: Service Unavailable.

Body

In the POST request, the server will return a list of  URLs queried in the response body when at least one of the queried URLs matches in the suspected phishing or malware lists.
POST_RESP_BODY = VERDICT (LF VERDICT)*
VERDICT = “phishing” | “malware” | “phishing,malware” | “ok”

The type has the same meaning as in the GET response body except that some of the URLs may be legitimate (recall that the server returns empty content only when none of the queried URLs matches the phishing or malware lists). In this case, we return “ok” for the non-matching URLs. The results are separated by the LF. There is a one-on-one mapping between the results in the response body and the queried URLs in the request body. For example, assume there are 10 URLs specified in the request body, the server will return exactly 10 results with the original order. That is, the first line corresponds to the result of the first queried URL, the second line corresponds to the result of the second queried URL, and so on.

Delphi Code for the POST Request

{$APPTYPE CONSOLE}
uses
  Classes,
  Windows,
  WinInet,
  SysUtils;

const
  sUserAgent = 'Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101';
  //¡¡¡¡¡¡¡¡¡¡Please be nice and use your own API key, get a key from here http://code.google.com/apis/safebrowsing/key_signup.html ¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡
  sApiKey    = 'ABQIAAAAzY4CKjsBFYV4Rxx0ZQaKlxQL2a1oqOk9I7UVXAZVtWa6uSA2XA';
  sServer    = 'sb-ssl.google.com';
  sPostSafeBrowsing  = '/safebrowsing/api/lookup?client=delphi&apikey=%s&appver=1.5.2&pver=3.0';

function GetWinInetError(ErrorCode:Cardinal): string;
const
   winetdll = 'wininet.dll';
var
  Len: Integer;
  Buffer: PChar;
begin
  Len := FormatMessage(
  FORMAT_MESSAGE_FROM_HMODULE or FORMAT_MESSAGE_FROM_SYSTEM or
  FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_IGNORE_INSERTS or  FORMAT_MESSAGE_ARGUMENT_ARRAY,
  Pointer(GetModuleHandle(winetdll)), ErrorCode, 0, @Buffer, SizeOf(Buffer), nil);
  try
    while (Len > 0) and {$IFDEF UNICODE}(CharInSet(Buffer[Len - 1], [#0..#32, '.'])) {$ELSE}(Buffer[Len - 1] in [#0..#32, '.']) {$ENDIF} do Dec(Len);
    SetString(Result, Buffer, Len);
  finally
    LocalFree(HLOCAL(Buffer));
  end;
end;

function Https_Post(const ServerName,Resource: String;const PostData : AnsiString;Var Response:AnsiString): Integer;
const
  BufferSize=1024*64;
var
  hInet    : HINTERNET;
  hConnect : HINTERNET;
  hRequest : HINTERNET;
  ErrorCode : Integer;
  lpdwBufferLength: DWORD;
  lpdwReserved    : DWORD;
  dwBytesRead     : DWORD;
  lpdwNumberOfBytesAvailable: DWORD;
begin
  Result  :=0;
  Response:='';
  hInet := InternetOpen(PChar(sUserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

  if hInet=nil then
  begin
    ErrorCode:=GetLastError;
    raise Exception.Create(Format('InternetOpen Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
  end;

  try
    hConnect := InternetConnect(hInet, PChar(ServerName), INTERNET_DEFAULT_HTTPS_PORT, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);
    if hConnect=nil then
    begin
      ErrorCode:=GetLastError;
      raise Exception.Create(Format('InternetConnect Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
    end;

    try
      hRequest := HttpOpenRequest(hConnect, 'POST', PChar(Resource), HTTP_VERSION, '', nil, INTERNET_FLAG_SECURE, 0);
      if hRequest=nil then
      begin
        ErrorCode:=GetLastError;
        raise Exception.Create(Format('HttpOpenRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
      end;

      try
        //send the post request
        if not HTTPSendRequest(hRequest, nil, 0, @PostData[1], Length(PostData)) then
        begin
          ErrorCode:=GetLastError;
          raise Exception.Create(Format('HttpSendRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
        end;

          lpdwBufferLength:=SizeOf(Result);
          lpdwReserved    :=0;
          //get the response code
          if not HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE or HTTP_QUERY_FLAG_NUMBER, @Result, lpdwBufferLength, lpdwReserved) then
          begin
            ErrorCode:=GetLastError;
            raise Exception.Create(Format('HttpQueryInfo Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
          end;

         //if the response code =200 then get the body
         if Result=200 then
          if InternetQueryDataAvailable(hRequest, lpdwNumberOfBytesAvailable, 0, 0) then
          begin
            SetLength(Response,lpdwNumberOfBytesAvailable);
            InternetReadFile(hRequest, @Response[1], lpdwNumberOfBytesAvailable, dwBytesRead);
          end
          else
          begin
            ErrorCode:=GetLastError;
            raise Exception.Create(Format('InternetQueryDataAvailable Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
          end;

      finally
        InternetCloseHandle(hRequest);
      end;
    finally
      InternetCloseHandle(hConnect);
    end;
  finally
    InternetCloseHandle(hInet);
  end;
end;

function URLEncode(const Url: string): string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to Length(Url) do
  begin
    case Url[i] of
      'A'..'Z', 'a'..'z', '0'..'9', '-', '_', '.':
        Result := Result + Url[i];
    else
        Result := Result + '%' + IntToHex(Ord(Url[i]), 2);
    end;
  end;
end;

Procedure TestPost(const UrlList : Array of AnsiString);
var
 Response     : AnsiString;
 ResponseCode : Integer;
 Data         : AnsiString;
 i            : integer;
 LstUrl       : TStringList;
begin
   //create the body request with the url to lookup
   Data:=AnsiString(IntToStr(Length(UrlList)))+#10;
   for i:= low(UrlList) to high(UrlList) do
     Data:=Data+UrlList[i]+#10;

   //make the post request
   ResponseCode:=Https_Post(sServer,Format(sPostSafeBrowsing,[sApiKey]), Data, Response);

   //process the response
   case ResponseCode of
     200:
          begin
             LstUrl:=TStringList.Create;
             try
               LstUrl.Text:=string(Response);
                for i:=0 to  LstUrl.Count-1  do
                 Writeln(Format('The queried URL (%s) is %s',[UrlList[i],LstUrl[i]]));

             finally
               LstUrl.Free;
             end;
          end;
     204: Writeln('NONE of the queried URLs matched the phishing or malware lists, no response body returned');
     400: Writeln('Bad Request — The HTTP request was not correctly formed.');
     401: Writeln('Not Authorized — The apikey is not authorized');
     503: Writeln('Service Unavailable — The server cannot handle the request.');
   else
         Writeln(Format('Unknow response Code (%d)',[ResponseCode]));
   end;
end;

begin
  try
     //check these three urls at once
     TestPost(['orgsite.info','http://www.google.com','http://malware.testing.google.test/testing/malware/']);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

finally the result will be

The queried URL (orgsite.info) is malware
The queried URL (http://www.google.com) is ok
The queried URL (http://malware.testing.google.test/testing/malware/) is malware


1 Comment

Delphi Preview Handler – source code published in google code

Update : this project now is hosted on Github.

A few hours ago I  published in the google code site the code of the Delphi Preview Handler project, under the Mozilla Public License 1.1 , now you can browse in the code and make your suggestions and report bugs using the issue page of the project. I also want to thank to Uwe Raabe by allow me use part of his work in this article to rewrite some parts of the preview handler.


22 Comments

Using the Google Maps API V3 from Delphi – Part III Getting the latitude and longitude of a mouse click

In this post I will show, how you can interact with a google map embedded in a TWebbrowser component in order to get the location ( latitude and longitude) of  a point when you click in the map.

JavaScript

To get the location of the mouse when you make a click in the map you must add a  Google maps Event Listener, passing a function to process the event, the values of the current location are retrieved in the event.latLng variable , the next step is store the values returned in a hidden field element to after get these values from Delphi.

Check this sample JavaScript snippet which create an event listener and store the values in the LatValue and LngValue fields.

    google.maps.event.addListener(map, "click",
         function(event)
           {
            document.getElementById("LatValue").value = event.latLng.lat();
            document.getElementById("LngValue").value = event.latLng.lng();
            PutMarker(document.getElementById("LatValue").value, document.getElementById("LngValue").value,"")
           }
   );

This is the PutMarker function which creates a marker in the current location

function PutMarker(Lat, Lang, Msg)
{
 var latlng = new google.maps.LatLng(Lat,Lang);
 var marker = new google.maps.Marker({
     position: latlng,
     map: map,
     title: Msg+" ("+Lat+","+Lang+")"
  });

   //put the created marker in an array
   markersArray.push(marker);

   //compute the index to associate an image to the marker
   index= (markersArray.length % 10);
   if (index==0) { index=10 }
   icon = "http://www.google.com/mapfiles/kml/paddle/"+index+"-lv.png";
   marker.setIcon(icon);
 }

And this is the code to create the 2 input hidden fields in the html page to store the values returned by the Event listener

<body onload="initialize()">
  <div id="map_canvas" style="width:100%; height:100%"></div>
  <div id="latlong">
  <input id="<span class=" type="hidden" />LatValue" >
  <input id="<span class=" type="hidden" />LngValue" >
  </div>
</body>

Delphi

Now from the Delphi side, you must detect the click event in the TWebBrowser component and then read the values stored in the hidden fields. Exists several ways to detect the click in the TWebBrowser, in this case I will use the OnCommandStateChange event.

Check this code which detect the click event and then read the values stored in the hidden fields.

procedure TForm1.WebBrowser1CommandStateChange(ASender: TObject;  Command: Integer; Enable: WordBool);
var
  ADocument : IHTMLDocument2;
  ABody     : IHTMLElement2;
  Lat : string;
  Lng : string;

      //get the value from a field
      function GetIdValue(const Id : string):string;
      var
        Tag      : IHTMLElement;
        TagsList : IHTMLElementCollection;
        Index    : Integer;
      begin
        Result:='';
        TagsList := ABody.getElementsByTagName('input');
        for Index := 0 to TagsList.length-1 do
        begin
          Tag:=TagsList.item(Index, EmptyParam) As IHTMLElement;
          if CompareText(Tag.id,Id)=0 then
            Result := Tag.getAttribute('value', 0);
        end;
      end;

begin
  //is a valid command?
  if TOleEnum(Command) <> CSC_UPDATECOMMANDS then //-1
    Exit;

  //The page is loaded?
  ADocument := WebBrowser1.Document as IHTMLDocument2;
  if not Assigned(ADocument) then
    Exit;

  //the page has body?
  if not Supports(ADocument.body, IHTMLElement2, ABody) then
    exit;

  // get the values of the Latitude and Longitude
  Lat :=GetIdValue('LatValue');
  Lng :=GetIdValue('LngValue');

  //Now process the data
  if  (Lat<>'') and (Lng<>'') and ((Lat<>Latitude.Text) or (Lng<>Longitude.Text)) then
  begin
    Latitude.Text :=Lat;
    Longitude.Text:=Lng;

  end;
end;

Finally this is the full source code for the demo application

unit uMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleCtrls, SHDocVw, StdCtrls, ExtCtrls, XPMan, ComCtrls,MSHTML;

type
  TFrmMain = class(TForm)
    WebBrowser1: TWebBrowser;
    PanelHeader: TPanel;
    ButtonGotoLocation: TButton;
    XPManifest1: TXPManifest;
    LabelLatitude: TLabel;
    LabelLongitude: TLabel;
    Longitude: TEdit;
    Latitude: TEdit;
    ButtonClearMarkers: TButton;
    ListView1: TListView;
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure ButtonClearMarkersClick(Sender: TObject);
    procedure WebBrowser1CommandStateChange(ASender: TObject; Command: Integer;  Enable: WordBool);
    procedure ButtonGotoLocationClick(Sender: TObject);
  private
    HTMLWindow2: IHTMLWindow2;
    procedure AddLatLngToList(const Lat,Lng:string);
  public
  end;

var
  FrmMain: TFrmMain;

implementation

{$R *.dfm}

uses
   ActiveX;

const
HTMLStr: AnsiString =
'<html> '+
'<head> '+
'<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" /> '+
'<script type="text/javascript">// <![CDATA[
src</span>="http://maps.google.com/maps/api/js?sensor=false&language=en">
// ]]></script> '+
//'<script type="text/javascript">// <![CDATA[
src</span>="http://maps.google.com/maps/api/js?sensor=false">
// ]]></script> '+
'<script type="text/javascript"> '+
''+
''+
'  var geocoder; '+
'  var map;  '+
'  var markersArray = [];'+
''+
''+
'  function initialize() { '+
'    geocoder = new google.maps.Geocoder();'+
'    var latlng = new google.maps.LatLng(40.714776,-74.019213); '+
'    var myOptions = { '+
'      zoom: 13, '+
'      center: latlng, '+
'      mapTypeId: google.maps.MapTypeId.ROADMAP '+
'    }; '+
'    map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); '+
'    map.set("streetViewControl", false);'+
'    google.maps.event.addListener(map, "click", '+
'         function(event) '+
'                        {'+
'                         document.getElementById("LatValue").value = event.latLng.lat(); '+
'                         document.getElementById("LngValue").value = event.latLng.lng(); '+
'                         PutMarker(document.getElementById("LatValue").value, document.getElementById("LngValue").value,"") '+
'                        } '+
'   ); '+
''+
'  } '+
''+
''+
'  function GotoLatLng(Lat, Lang) { '+
'   var latlng = new google.maps.LatLng(Lat,Lang);'+
'   map.setCenter(latlng);'+
'  }'+
''+
''+
'function ClearMarkers() {  '+
'  if (markersArray) {        '+
'    for (i in markersArray) {  '+
'      markersArray[i].setMap(null); '+
'    } '+
'  } '+
'}  '+
''+
'  function PutMarker(Lat, Lang, Msg) { '+
'   var latlng = new google.maps.LatLng(Lat,Lang);'+
'   var marker = new google.maps.Marker({'+
'      position: latlng, '+
'      map: map,'+
'      title: Msg+" ("+Lat+","+Lang+")"'+
'  });'+
'  markersArray.push(marker); '+
'  index= (markersArray.length % 10);'+
'  if (index==0) { index=10 } '+
'  icon = "http://www.google.com/mapfiles/kml/paddle/"+index+"-lv.png"; '+
'  marker.setIcon(icon); '+
'  }'+
''+
''+
''+'</script> '+
'</head> '+
''+
'<body onload="initialize()"> '+
'  <div id="map_canvas" style="width:100%; height:100%"></div> '+
'  <div id="latlong"> '+
'  <input type="hidden" id="LatValue" >'+
'  <input type="hidden" id="LngValue" >'+
'  </div>  '+
''+
'</body> '+
'</html> ';

procedure TFrmMain.FormCreate(Sender: TObject);
var
  aStream     : TMemoryStream;
begin
   WebBrowser1.Navigate('about:blank');
    if Assigned(WebBrowser1.Document) then
    begin
      aStream := TMemoryStream.Create;
      try
         aStream.WriteBuffer(Pointer(HTMLStr)^, Length(HTMLStr));
         //aStream.Write(HTMLStr[1], Length(HTMLStr));
         aStream.Seek(0, soFromBeginning);
         (WebBrowser1.Document as IPersistStreamInit).Load(TStreamAdapter.Create(aStream));
      finally
         aStream.Free;
      end;
      HTMLWindow2 := (WebBrowser1.Document as IHTMLDocument2).parentWindow;
    end;
end;

procedure TFrmMain.WebBrowser1CommandStateChange(ASender: TObject;  Command: Integer; Enable: WordBool);
var
  ADocument : IHTMLDocument2;
  ABody     : IHTMLElement2;
  Lat : string;
  Lng : string;

      function GetIdValue(const Id : string):string;
      var
        Tag      : IHTMLElement;
        TagsList : IHTMLElementCollection;
        Index    : Integer;
      begin
        Result:='';
        TagsList := ABody.getElementsByTagName('input');
        for Index := 0 to TagsList.length-1 do
        begin
          Tag:=TagsList.item(Index, EmptyParam) As IHTMLElement;
          if CompareText(Tag.id,Id)=0 then
            Result := Tag.getAttribute('value', 0);
        end;
      end;

begin
  if TOleEnum(Command) <> CSC_UPDATECOMMANDS then
    Exit;

  ADocument := WebBrowser1.Document as IHTMLDocument2;
  if not Assigned(ADocument) then
    Exit;

  if not Supports(ADocument.body, IHTMLElement2, ABody) then
    exit;

  Lat :=GetIdValue('LatValue');
  Lng :=GetIdValue('LngValue');
  if  (Lat<>'') and (Lng<>'') and ((Lat<>Latitude.Text) or (Lng<>Longitude.Text)) then
  begin
    Latitude.Text :=Lat;
    Longitude.Text:=Lng;
    AddLatLngToList(Lat, Lng);
  end;
end;

procedure TFrmMain.AddLatLngToList(const Lat, Lng: string);
var
  Item  : TListItem;
begin
   if (Lat<>'') and (Lng<>'') then
   begin
     Item:=ListView1.Items.Add;
     Item.Caption:=Lng;
     Item.SubItems.Add(Lat);
     Item.MakeVisible(False);
   end;
end;

procedure TFrmMain.ButtonClearMarkersClick(Sender: TObject);
begin
  HTMLWindow2.execScript('ClearMarkers()', 'JavaScript');
  ListView1.Items.Clear;
end;

procedure TFrmMain.ButtonGotoLocationClick(Sender: TObject);
begin
  if Assigned(ListView1.Selected) then
    HTMLWindow2.execScript(Format('GotoLatLng(%s,%s)',[ListView1.Selected.SubItems[0],ListView1.Selected.Caption]), 'JavaScript');
end;

end.

Check the source code on Github.


2 Comments

WMI Delphi Code Creator – New features and source code published in google code

Update : The WMI Delphi Code Creator now is hosted on Github.

In the last months I’ been working in a new version of the WMI Delphi Code Creator, porting the original code from Delphi 2007 to XE, adding new features like support for Delphi Prism and Free Pascal, and improving the source code generated by the tool. Now is time to show the results, so the new version is here with many new features,  also  the full source code is now available in the google code project hosting site under the Mozilla Public License 1.1

 

Check the list of features of the current version

  • Can generate object pascal code compatible with one of these compilers Delphi Win32, Delphi -Prism (Oxygene) , Free Pascal
  • The Delphi code generated is compatible with  Delphi 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE.
  • The Free Pascal code generated is compatible with these versions 2.4.2, 2.4.4
  • The Delphi prism .Net  (Oxygene) generated code is compatible with all the versions up to 4.0.23.741 (in newer versions must work too)
  • Full access to metadata of any WMI Class registered in the system including qualifiers, mof definition, properties, methods, events
  • You can access directly from the application the MSDN web page related to the WMI Class which your are using.
  • Compile and run the generated code directly form the application using the selected compiler.
  • Open the the generated Delphi code in any of these  Delphi IDE’s 7, 2005, BDS/Turbo 2006 and RAD Studio 2007, 2009, 2010, XE
  • Open the the generated Free Pascal code directly in the Lazarus IDE.
  • Open the the generated Delphi Prism code directly in Visual Studio 2008, Visual Studio 2010 or MonoDevelop.
  • Runs on Windows XP, 2003, 2008, Vista and 7.
  • Themes support for Syntax highlighting (+50 themes included) compatible with the Delphi IDE Theme Editor.

For download the new version of the application and more details go to project page


Leave a comment

How get and parse a manifest of an external application using delphi

A manifest is basically a XML file that contains settings that informs Windows how to handle a program when it is started.  The manifest can be embedded inside the program file (as a resource) or it can be located in a separate external XML file. In this article I will show how you can read a embedded windows application manifest from a exe using delphi and parse the information contained using XPath.

The manifest are full of rich information which you can use to determine for example the Requested Execution Levels  or the version of the comctl32.dll used by an application.

To read the manifest from a exe file you must use the LoadLibraryEx function with the LOAD_LIBRARY_AS_DATAFILE flag (or since windows vista you can use the LOAD_LIBRARY_AS_IMAGE_RESOURCE value instead) and the TResourceStream class.

Check this sample code which returns the manifest from a exe file as a string;

function  GetManifest(const FileName:string) : AnsiString;
var
  hModule  : THandle;
  Resource : TResourceStream;
begin
  Result:='';
  //load the file to read
  hModule:=LoadLibraryEx(PChar(FileName),0,LOAD_LIBRARY_AS_DATAFILE);
  try
     if hModule=0 then RaiseLastOSError;
     //check if exist the manifest inside of the file
     if FindResource(hModule, MakeIntResource(1), RT_MANIFEST)<>0 then
     begin
       //load the resource
       Resource:=TResourceStream.CreateFromID(hModule,1,RT_MANIFEST);
       try
         SetString(Result, PAnsiChar(Resource.Memory),Resource.Size);
       finally
         Resource.Free;
       end;
     end;
  finally
      FreeLibrary(hModule);
  end;
end;

Ok that was the easy part, now before to parse the xml check a sample manifest file generated by Delphi XE

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity
    type="win32"
    name="CodeGear RAD Studio"
    version="15.0.3890.34076" 
    processorArchitecture="*"/>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
        type="win32"
        name="Microsoft.Windows.Common-Controls"
        version="6.0.0.0"
        publicKeyToken="6595b64144ccf1df"
        language="*"
        processorArchitecture="*"/>
    </dependentAssembly>
  </dependency>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel
          level="asInvoker"
          uiAccess="false"/>
        </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

As you can see exist two XML namespaces (urn:schemas-microsoft-com:asm.v1 and urn:schemas-microsoft-com:asm.v3) inside of the xml file, before to read the xml string using XPath you must consider these two namespaces.

See this code which deal with the xml and the namespaces

//the namespaces used
const
 assembly_namespace_V1='urn:schemas-microsoft-com:asm.v1';
 assembly_namespace_V2='urn:schemas-microsoft-com:asm.v2';
 assembly_namespace_V3='urn:schemas-microsoft-com:asm.v3';
var
  XmlDoc : OleVariant;
  ns     : string;
  Node   : OleVariant;
begin
  if Trim(FManifest)='' then exit;
  //create a Xml Dom instance
  XmlDoc       := CreateOleObject('Msxml2.DOMDocument.6.0');
  XmlDoc.Async := False;
  try
    //load the Xml string
    XmlDoc.LoadXML(FManifest);
    XmlDoc.SetProperty('SelectionLanguage','XPath');

    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);

    //set the namespaces alias
    ns := Format('xmlns:a=%s xmlns:b=%s xmlns:c=%s',[QuotedStr(assembly_namespace_V1),QuotedStr(assembly_namespace_V2),QuotedStr(assembly_namespace_V3)]);
    XmlDoc.setProperty('SelectionNamespaces', ns);

    //get the version of the manifest
    Node:=XmlDoc.selectSingleNode('/a:assembly/@manifestVersion');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    FManifestVersion:=Node.text;

    //parsing then Assembly Identity
    Node:=XmlDoc.selectSingleNode('/a:assembly/a:assemblyIdentity');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FMainAssemblyIdentity.&type   :=Node.getAttribute('type');
      FMainAssemblyIdentity.name    :=Node.getAttribute('name');
      FMainAssemblyIdentity.language:=VarNullToStr(Node.getAttribute('language'));
      FMainAssemblyIdentity.version :=Node.getAttribute('version');
      FMainAssemblyIdentity.processorArchitecture:=VarNullToStr(Node.getAttribute('processorArchitecture'));
      FMainAssemblyIdentity.publicKeyToken       :=VarNullToStr(Node.getAttribute('publicKeyToken'));
    end;

    Node:=XmlDoc.selectSingleNode('/a:assembly/a:dependency/a:dependentAssembly/a:assemblyIdentity');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FDependentAssembly.&type   :=Node.getAttribute('type');
      FDependentAssembly.name    :=Node.getAttribute('name');
      FDependentAssembly.language:=VarNullToStr(Node.getAttribute('language'));
      FDependentAssembly.version :=Node.getAttribute('version');
      FDependentAssembly.processorArchitecture:=VarNullToStr(Node.getAttribute('processorArchitecture'));
      FDependentAssembly.publicKeyToken       :=VarNullToStr(Node.getAttribute('publicKeyToken'));
    end;

    //Now the tricky part. The requestedExecutionLevel can be located in one of these namespaces
    //urn:schemas-microsoft-com:asm.v2 or urn:schemas-microsoft-com:asm.v3
    Node:=XmlDoc.selectSingleNode('/a:assembly/b:trustInfo/b:security/b:requestedPrivileges/b:requestedExecutionLevel');
    //if not found the requestedExecutionLevel then
    if VarIsNull(Node) or VarIsClear(Node) then
    //try with the next namespace
      Node:=XmlDoc.selectSingleNode('/a:assembly/c:trustInfo/c:security/c:requestedPrivileges/c:requestedExecutionLevel');
    //contains data?
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FRequestedExecutionLevel.level   :=Node.getAttribute('level');
      FRequestedExecutionLevel.uiAccess:=VarNullToStr(Node.getAttribute('uiAccess'));
    end;

  finally
    XmlDoc:=Unassigned;
  end;
end;

Finally check this class to read the content of an Manifest embedded in exe file.

{$APPTYPE CONSOLE}

uses
  ActiveX,
  Classes,
  Windows,
  Variants,
  ComObj,
  StrUtils,
  SysUtils;

type
  TAssemblyIdentity=record
    &type : string;
    name	: string;
    language: string;
    processorArchitecture	: string;
    version	: string;
    publicKeyToken: string;
  end;

  TRequestedExecutionLevel=record
    level    : string;
    uiAccess : string;
  end;

  TManifiestReader=class
  private
    FFileName: string;
    FManifest: AnsiString;
    FMainAssemblyIdentity: TAssemblyIdentity;
    FHasManifest: Boolean;
    FDependentAssembly: TAssemblyIdentity;
    FManifestVersion: string;
    FRequestedExecutionLevel: TRequestedExecutionLevel;
    procedure GetManifest;
    procedure LoadManifestData;
    function  VarNullToStr(Value:OleVariant):string;
  public
    property FileName : string read FFileName;
    property Manifest : AnsiString read FManifest;
    property ManifestVersion : string read FManifestVersion;
    property MainAssemblyIdentity : TAssemblyIdentity read FMainAssemblyIdentity;
    property DependentAssembly : TAssemblyIdentity read FDependentAssembly;
    property HasManifest : Boolean read FHasManifest;
    property RequestedExecutionLevel : TRequestedExecutionLevel read FRequestedExecutionLevel;
    constructor Create(const AFileName:string);
  end;

{ TReadManifiest }

constructor TManifiestReader.Create(const AFileName: string);
begin
  FFileName:=AFileName;
  FHasManifest:=False;
  GetManifest;
  LoadManifestData;
end;

procedure TManifiestReader.GetManifest;
var
  hModule  : THandle;
  Resource : TResourceStream;
begin
  FManifest:='';
  hModule:=LoadLibraryEx(PChar(FileName),0,LOAD_LIBRARY_AS_DATAFILE);
  try
     if hModule=0 then RaiseLastOSError;
     if FindResource(hModule, MakeIntResource(1), RT_MANIFEST)<>0 then
     begin
       Resource:=TResourceStream.CreateFromID(hModule,1,RT_MANIFEST);
       try
         SetString(FManifest, PAnsiChar(Resource.Memory),Resource.Size);
         FHasManifest:=True;
       finally
         Resource.Free;
       end;
     end;
  finally
      FreeLibrary(hModule);
  end;
end;

procedure TManifiestReader.LoadManifestData;
const
 assembly_namespace_V1='urn:schemas-microsoft-com:asm.v1';
 assembly_namespace_V2='urn:schemas-microsoft-com:asm.v2';
 assembly_namespace_V3='urn:schemas-microsoft-com:asm.v3';
var
  XmlDoc : OleVariant;
  ns     : string;
  Node   : OleVariant;
begin
  if Trim(FManifest)='' then exit;
  XmlDoc       := CreateOleObject('Msxml2.DOMDocument.6.0');
  XmlDoc.Async := False;
  try
    XmlDoc.LoadXML(FManifest);
    XmlDoc.SetProperty('SelectionLanguage','XPath');

    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);

    //set the namespaces alias
    ns := Format('xmlns:a=%s xmlns:b=%s xmlns:c=%s',[QuotedStr(assembly_namespace_V1),QuotedStr(assembly_namespace_V2),QuotedStr(assembly_namespace_V3)]);
    XmlDoc.setProperty('SelectionNamespaces', ns);

    //get the version of the manifest
    Node:=XmlDoc.selectSingleNode('/a:assembly/@manifestVersion');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    FManifestVersion:=Node.text;

    Node:=XmlDoc.selectSingleNode('/a:assembly/a:assemblyIdentity');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FMainAssemblyIdentity.&type   :=Node.getAttribute('type');
      FMainAssemblyIdentity.name    :=Node.getAttribute('name');
      FMainAssemblyIdentity.language:=VarNullToStr(Node.getAttribute('language'));
      FMainAssemblyIdentity.version :=Node.getAttribute('version');
      FMainAssemblyIdentity.processorArchitecture:=VarNullToStr(Node.getAttribute('processorArchitecture'));
      FMainAssemblyIdentity.publicKeyToken       :=VarNullToStr(Node.getAttribute('publicKeyToken'));
    end;

    Node:=XmlDoc.selectSingleNode('/a:assembly/a:dependency/a:dependentAssembly/a:assemblyIdentity');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FDependentAssembly.&type   :=Node.getAttribute('type');
      FDependentAssembly.name    :=Node.getAttribute('name');
      FDependentAssembly.language:=VarNullToStr(Node.getAttribute('language'));
      FDependentAssembly.version :=Node.getAttribute('version');
      FDependentAssembly.processorArchitecture:=VarNullToStr(Node.getAttribute('processorArchitecture'));
      FDependentAssembly.publicKeyToken       :=VarNullToStr(Node.getAttribute('publicKeyToken'));
    end;

    Node:=XmlDoc.selectSingleNode('/a:assembly/b:trustInfo/b:security/b:requestedPrivileges/b:requestedExecutionLevel');
    if VarIsNull(Node) or VarIsClear(Node) then
      Node:=XmlDoc.selectSingleNode('/a:assembly/c:trustInfo/c:security/c:requestedPrivileges/c:requestedExecutionLevel');
    if not VarIsNull(Node) and not VarIsClear(Node) then
    begin
      FRequestedExecutionLevel.level   :=Node.getAttribute('level');
      FRequestedExecutionLevel.uiAccess:=VarNullToStr(Node.getAttribute('uiAccess'));
    end;

  finally
    XmlDoc:=Unassigned;
  end;
end;

function TManifiestReader.VarNullToStr(Value: OleVariant): string;
begin
  if VarIsNull(Value) then
    Result:=''
  else
    Result:=VarToStr(Value);
end;

Var
  ManifestReader : TManifiestReader;
begin
 try
    CoInitialize(nil);
    try
      ManifestReader:=TManifiestReader.Create('MyApplication.exe');
      try
        //Writeln(ManifestReader.Manifest);

        Writeln('Manifest version '+ManifestReader.ManifestVersion);
        Writeln('Main Assembly Identity');
        Writeln('----------------------');
        Writeln('type     '+ManifestReader.MainAssemblyIdentity.&type);
        Writeln('name     '+ManifestReader.MainAssemblyIdentity.name);
        Writeln('language '+ManifestReader.MainAssemblyIdentity.language);
        Writeln('version  '+ManifestReader.MainAssemblyIdentity.version);
        Writeln('processorArchitecture '+ManifestReader.MainAssemblyIdentity.processorArchitecture);
        Writeln('publicKeyToken        '+ManifestReader.MainAssemblyIdentity.publicKeyToken);
        Writeln('');

        Writeln('Dependent Assembly Identity');
        Writeln('---------------------------');
        Writeln('type     '+ManifestReader.DependentAssembly.&type);
        Writeln('name     '+ManifestReader.DependentAssembly.name);
        Writeln('language '+ManifestReader.DependentAssembly.language);
        Writeln('version  '+ManifestReader.DependentAssembly.version);
        Writeln('processorArchitecture '+ManifestReader.DependentAssembly.processorArchitecture);
        Writeln('publicKeyToken        '+ManifestReader.DependentAssembly.publicKeyToken);
        Writeln('');

        Writeln('Requested Execution Level');
        Writeln('---------------------------');
        Writeln('level     '+ManifestReader.RequestedExecutionLevel.level);
        Writeln('uiAccess  '+ManifestReader.RequestedExecutionLevel.uiAccess);

      finally
        ManifestReader.Free;
      end;
    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
  Readln;
end.

Additional resources


62 Comments

Introducing the Delphi Preview Handler

To download the last version of the delphi preview handler check the new page of the project.

 
The current beta version of the preview handler (DelphiPreviewHandler.dll)  is 1.0.1.168  please check the version installed before you report bugs.

Some weeks ago I began a parallel project to the Delphi IDE Theme Editor, I name this project Delphi Preview Handler, which basically is a preview handler for windows vista and  7 which allow you read your object pascal, C++ and Assembly code with Syntax highlighting without open in a editor, this preview handler can render these file extensions .pp, .lpr, .lfm, .lpk, .inc, .pas, .dpr,.dfm, .dpk,.dproj, .bdsproj,.c, .cpp,. cc,.h, .hpp,.hh, .cxx, .hxx, . cu, .asm.

Features

  • Supports Windows Vista y Windows 7 32 and 64 bits.
  • +50 themes included to see your code with style
  • Themes compatibles with the Delphi IDE Theme Editor
  • Support these file extensions .pp, .lpr, .lfm, .lpk, .inc, .pas, .dpr,.dfm, .dpk,.dproj, .bdsproj,.c, .cpp,. cc,.h, .hpp,.hh, .cxx, .hxx, . cu, .asm.

Technical Stuff

  • Written in Delphi XE
  • Components used SynEdit

Screenshots

Video

Source Code

Soon, very soon.

Manual Installation

Follow these steps to register the preview handler

1. Choose Start > All Programs > Accessories.
2. Right-click on Command Prompt, Select Run As Administrator, and Authenticate.
3. Go to Delphi Preview Hander folder in Command Prompt.
4. Run “Register.bat”
5. Enjoy

Important Note about editing the Settings.ini file

You can change  the theme used in the Preview Handler editing the Settings.ini file, but before to do this you must unregister the preview handler and close all the explorer windows. to avoid problems. please follow these steps :

1. unregister the dll

2.close all the windows explorer

3.edit the Settings.ini file

4.save the changes

5.register the dll

6.enjoy

Important Note about installing  a new version

In order to avoid problems you must follow these steps when you install or register a new version of the preview handler.

1. close all the windows explorer windows which have the preview handler active or the preview handler was used (remember the dll remains in memory until the windows explorer was closed)

2. unregister the previous version executing the uninstaller located in C:\Program Files (x86)\TheRoadToDelphi\DelphiPreviewHandler or C:\Program Files\TheRoadToDelphi\DelphiPreviewHandler

3. If you install the preview handler manually you must unregister using the UnRegister.bat  running as admin.

4.Now proceed with the installation of the new version.

Download the Binaries of the Delphi Preview Handler from here

Download the Installer (recommended) of the Delphi Preview Handler from here

All your suggestions and comments are very welcome.


22 Comments

Using the Microsoft Translator V2 API from delphi

Due which the Google Translate API has been officially deprecated is time to look for alternatives and a good one is the Microsoft Translator V2, in this article I will show how you can access this API from a Delphi desktop application.

In order to gain access to this API you must obtain a Bing AppID, so go to this page register and get your Bing AppID to play with this.

The Microsoft Translator V2 can be accessed via HTTP, Ajax or SOAP in this post we will use the HTTP interface to make the requests. Now to start using the HTTP API for the Microsoft Translator service you must send a request to the appropriate http://api.microsofttranslator.com/V2/HTTP.svc url and then parse the returned response.

This is the list of the functions supported by the HTTP API

Name Description
Microsoft.Translator.AddTranslation Method Adds a translation to the translation memory.
Microsoft.Translator.AddTranslationArray Method Adds an array of translations to the translation memory.
Microsoft.Translator.BreakSentences Method Returns an array of sentence lengths for each sentence of the given text.
Microsoft.Translator.Detect Method Detects the language of a selection of text.
Microsoft.Translator.DetectArray Method Detects the language of an array of strings.
Microsoft.Translator.GetAppIdToken Method Returns a tokenized AppID which can be used as AppID parameter in any method.
Microsoft.Translator.GetLanguageNames Method Obtains a list of the languages supported by the Translator Service.
Microsoft.Translator.GetLanguagesForSpeak Method Obtains a list of the language codes supported by the Translator Service for speech synthesis.
Microsoft.Translator.GetLanguagesForTranslate Method Obtains a list of the language codes supported by the Translator Service.
Microsoft.Translator.GetTranslations Method Returns an array of alternative translations of the given text.
Microsoft.Translator.GetTranslationsArray Method Returns an array of alternative translations of the passed array of text.
Microsoft.Translator.Speak Method Returns a stream of a wave-file speaking the passed-in text in the desired language.
Microsoft.Translator.Translate Method Converts a text string from one language to another.
Microsoft.Translator.TranslateArray Method Translates an array of texts into another language.

The next samples uses two helper functions to make a http request (off course which you can use you own method or component too)

uses
  Windows,
  WinInet;

procedure WinInet_HttpGet(const Url: string;Stream:TStream);overload;
const
BuffSize = 1024*1024;
var
  hInter   : HINTERNET;
  UrlHandle: HINTERNET;
  BytesRead: DWORD;
  Buffer   : Pointer;
begin
  hInter := InternetOpen('', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  if Assigned(hInter) then
    try
      Stream.Seek(0,0);
      GetMem(Buffer,BuffSize);
      try
          UrlHandle := InternetOpenUrl(hInter, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);
          if Assigned(UrlHandle) then
          begin
            repeat
              InternetReadFile(UrlHandle, Buffer, BuffSize, BytesRead);
              if BytesRead>0 then
               Stream.WriteBuffer(Buffer^,BytesRead);
            until BytesRead = 0;
            InternetCloseHandle(UrlHandle);
          end;
      finally
        FreeMem(Buffer);
      end;
    finally
     InternetCloseHandle(hInter);
    end;
end;

function WinInet_HttpGet(const Url: string): string;overload;
Var
  StringStream : TStringStream;
begin
  Result:='';
    StringStream:=TStringStream.Create('',TEncoding.UTF8);
    try
        WinInet_HttpGet(Url,StringStream);
        if StringStream.Size>0 then
        begin
          StringStream.Seek(0,0);
          Result:=StringStream.ReadString(StringStream.Size);
        end;
    finally
      StringStream.Free;
    end;
end;

Translating a Text

To translate a given text you must use the Translate method  making  a request to this URL http://api.microsofttranslator.com/V2/Http.svc/Translate using these parameters

Parameter                                                 Description
appId A string containing the Bing AppID.
text A string representing the text to translate.
from A string representing the language code of the translation text.
to A string representing the language code to translate the text into.
contentType The format of the text being translated. The supported formats are “text/plain” and “text/html”. Any HTML needs to be well-formed.
category The category of the text to translate. The only supported category is “general”.

In delphi you can construct this URI in this way

const
  MicrosoftTranslatorTranslateUri = 'http://api.microsofttranslator.com/v2/Http.svc/Translate?appId=%s&text=%s&from=%s&to=%s';

Check this sample code which make a http request and a translate a text

function TranslateText(const AText,SourceLng,DestLng:string):string;
var
   XmlDoc : OleVariant;
   Node   : OleVariant;
begin
 //Make the http request
 Result:=WinInet_HttpGet(Format(MicrosoftTranslatorTranslateUri,[BingAppId,AText,SourceLng,DestLng]));
 //Create  a XML object o parse the result
  XmlDoc:= CreateOleObject(Msxml2_DOMDocument);
  try
    XmlDoc.Async := False;
    //load the XML retuned string
    XmlDoc.LoadXML(Result);
    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);
    Node:= XmlDoc.documentElement;
    if not VarIsClear(Node) then
     Result:=XmlDoc.Text;
  finally
     XmlDoc:=Unassigned;
  end;
end;

Detecting the language

To detect the language of a text you must use the Detect Method making a http request to this URI  http://api.microsofttranslator.com/V2/Http.svc/Detect with these parameters.

 Parameter Description
appId A string containing the Bing AppID.
text A string containing some text whose language is to be identified.

In delphi you can construct this URI in this way

const
  MicrosoftTranslatorDetectUri    = 'http://api.microsofttranslator.com/v2/Http.svc/Detect?appId=%s&text=%s';
function DetectLanguage(const AText:string ):string;
var
   XmlDoc : OleVariant;
   Node   : OleVariant;
begin
  //make the http request
  Result:=WinInet_HttpGet(Format(MicrosoftTranslatorDetectUri,[BingAppId,AText]));
  XmlDoc:= CreateOleObject(Msxml2_DOMDocument);
  try
    XmlDoc.Async := False;
    //load the returned xml string
    XmlDoc.LoadXML(Result);
    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);
    Node:= XmlDoc.documentElement;
   //get the detected language from the node
    if not VarIsClear(Node) then
      Result:=XmlDoc.Text;
  finally
     XmlDoc:=Unassigned;
  end;
end;
end;

Getting the list of supported languages

The GetLanguagesForTranslate method return a list of the supported languages for translation

The URL of this method is http://api.microsofttranslator.com/V2/Http.svc/GetLanguagesForTranslate and the parameters are

Parameter  Description
appId A string containing the Bing AppID.
The delphi equivalkent URI
const
MicrosoftTranslatorGetLngUri    = 'http://api.microsofttranslator.com/v2/Http.svc/GetLanguagesForTranslate?appId=%s';

And here you a have a sample code to make the request and parse the response

function GetLanguagesForTranslate: TList<string>;
var
   XmlDoc : OleVariant;
   Node   : OleVariant;
   Nodes  : OleVariant;
   lNodes : Integer;
   i      : Integer;
   sValue : string;
begin
  Result:=TList<string>.Create;
  //make the http request
  sValue:=WinInet_HttpGet(Format(MicrosoftTranslatorGetLngUri,[BingAppId]));
  XmlDoc:= CreateOleObject(Msxml2_DOMDocument);
  try
    XmlDoc.Async := False;
    XmlDoc.LoadXML(sValue);
    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);
    Node:= XmlDoc.documentElement;
    if not VarIsClear(Node) then
    begin
      //get the nodes
      Nodes := Node.childNodes;
       if not VarIsClear(Nodes) then
       begin
         lNodes:= Nodes.Length;
           for i:=0 to lNodes-1 do
            Result.Add(Nodes.Item(i).Text);
       end;
    end;
  finally
     XmlDoc:=Unassigned;
  end;
end;

Getting the list of supported languages for speak

the GetLanguagesForSpeak Method returns a list of the suported languages for speak, the URL for this method is http://api.microsofttranslator.com/V2/Http.svc/GetLanguagesForSpeak

Parameter Description
appId A string containing the Bing AppID.

the delphi equivalent declaration of the URI

const
  MicrosoftTranslatorGetSpkUri    = 'http://api.microsofttranslator.com/v2/Http.svc/GetLanguagesForSpeak?appId=%s';

Now the function to get the supported languages

function GetLanguagesForSpeak: TList<string>;
var
   XmlDoc : OleVariant;
   Node   : OleVariant;
   Nodes  : OleVariant;
   lNodes : Integer;
   i      : Integer;
   sValue : string;
begin
  Result:=TList<string>.Create;
  sValue:=WinInet_HttpGet(Format(MicrosoftTranslatorGetSpkUri,[BingAppId]));
  XmlDoc:= CreateOleObject(Msxml2_DOMDocument);
  try
    XmlDoc.Async := False;
    XmlDoc.LoadXML(sValue);
    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);
    Node:= XmlDoc.documentElement;
    if not VarIsClear(Node) then
    begin
      Nodes := Node.childNodes;
       if not VarIsClear(Nodes) then
       begin
         lNodes:= Nodes.Length;
           for i:=0 to lNodes-1 do
            Result.Add(Nodes.Item(i).Text);
       end;
    end;
  finally
     XmlDoc:=Unassigned;
  end;
end;

Making the API speak

The speak method returns a stream of a audio file speaking the passed-in text in the desired language. the URI of this function is http://api.microsofttranslator.com/V2/Http.svc/Speak and the parameters are

Parameter Description
appId A string containing the Bing AppID.
text A string containing a sentence or sentences of the specified language to be spoken for the wave stream.
language A string representing the supported language code to speak the text in. The code must be present in the list of codes returned from the method GetLanguagesForSpeak.
format Optional. A string specifying the format of the wafe-file to be returned. The default value is “audio/wav” which is the only currently allowed value.

The delphi equivalent URI

const
  MicrosoftTranslatorSpeakUri     = 'http://api.microsofttranslator.com/v2/Http.svc/Speak?appId=%s&text=%s&language=%s';

And the function to get the audio stream

procedure Speak(const FileName,AText,Lng:string);
var
  Stream : TFileStream;
begin
  Stream:=TFileStream.Create(FileName,fmCreate);
  try
    WinInet_HttpGet(Format(MicrosoftTranslatorSpeakUri,[BingAppId,AText,Lng]),Stream);
  finally
     Stream.Free;
  end;
end;

Finally this is the full code of a sample console application with all the funcions covered in this post

program MicrosoftTranslatorApi;

{$APPTYPE CONSOLE}

uses
  ShellApi,
  ActiveX,
  Classes,
  ComObj,
  Variants,
  Windows,
  WinInet,
  Generics.Collections,
  SysUtils;

const
   MicrosoftTranslatorTranslateUri = 'http://api.microsofttranslator.com/v2/Http.svc/Translate?appId=%s&text=%s&from=%s&to=%s';
   MicrosoftTranslatorDetectUri    = 'http://api.microsofttranslator.com/v2/Http.svc/Detect?appId=%s&text=%s';
   MicrosoftTranslatorGetLngUri    = 'http://api.microsofttranslator.com/v2/Http.svc/GetLanguagesForTranslate?appId=%s';
   MicrosoftTranslatorGetSpkUri    = 'http://api.microsofttranslator.com/v2/Http.svc/GetLanguagesForSpeak?appId=%s';
   MicrosoftTranslatorSpeakUri     = 'http://api.microsofttranslator.com/v2/Http.svc/Speak?appId=%s&text=%s&language=%s';
   //this AppId if for demo only please be nice and use your own , it's easy get one from here http://msdn.microsoft.com/en-us/library/ff512386.aspx
   BingAppId                       = '73C8F474CA4D1202AD60747126813B731199ECEA';
   Msxml2_DOMDocument              = 'Msxml2.DOMDocument.6.0';

procedure WinInet_HttpGet(const Url: string;Stream:TStream);overload;
const
BuffSize = 1024*1024;
var
  hInter   : HINTERNET;
  UrlHandle: HINTERNET;
  BytesRead: DWORD;
  Buffer   : Pointer;
begin
  hInter := InternetOpen('', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  if Assigned(hInter) then
    try
      Stream.Seek(0,0);
      GetMem(Buffer,BuffSize);
      try
          UrlHandle := InternetOpenUrl(hInter, PChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);
          if Assigned(UrlHandle) then
          begin
            repeat
              InternetReadFile(UrlHandle, Buffer, BuffSize, BytesRead);
              if BytesRead>0 then
               Stream.WriteBuffer(Buffer^,BytesRead);
            until BytesRead = 0;
            InternetCloseHandle(UrlHandle);
          end;
      finally
        FreeMem(Buffer);
      end;
    finally
     InternetCloseHandle(hInter);
    end;
end;

function WinInet_HttpGet(const Url: string): string;overload;
Var
  StringStream : TStringStream;
begin
  Result:='';
    StringStream:=TStringStream.Create('',TEncoding.UTF8);
    try
        WinInet_HttpGet(Url,StringStream);
        if StringStream.Size>0 then
        begin
          StringStream.Seek(0,0);
          Result:=StringStream.ReadString(StringStream.Size);
        end;
    finally
      StringStream.Free;
    end;
end;

function TranslateText(const AText,SourceLng,DestLng:string):string;
var
   XmlDoc : OleVariant;
   Node   : OleVariant;
begin
  Result:=WinInet_HttpGet(Format(MicrosoftTranslatorTranslateUri,[BingAppId,AText,SourceLng,DestLng]));
  XmlDoc:= CreateOleObject(Msxml2_DOMDocument);
  try
    XmlDoc.Async := False;
    XmlDoc.LoadXML(Result);
    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);
    Node:= XmlDoc.documentElement;
    if not VarIsClear(Node) then
     Result:=XmlDoc.Text;
  finally
     XmlDoc:=Unassigned;
  end;
end;

function DetectLanguage(const AText:string ):string;
var
   XmlDoc : OleVariant;
   Node   : OleVariant;
begin
  Result:=WinInet_HttpGet(Format(MicrosoftTranslatorDetectUri,[BingAppId,AText]));
  XmlDoc:= CreateOleObject(Msxml2_DOMDocument);
  try
    XmlDoc.Async := False;
    XmlDoc.LoadXML(Result);
    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);
    Node:= XmlDoc.documentElement;
    if not VarIsClear(Node) then
      Result:=XmlDoc.Text;
  finally
     XmlDoc:=Unassigned;
  end;
end;

function GetLanguagesForTranslate: TList<string>;
var
   XmlDoc : OleVariant;
   Node   : OleVariant;
   Nodes  : OleVariant;
   lNodes : Integer;
   i      : Integer;
   sValue : string;
begin
  Result:=TList<string>.Create;
  sValue:=WinInet_HttpGet(Format(MicrosoftTranslatorGetLngUri,[BingAppId]));
  XmlDoc:= CreateOleObject(Msxml2_DOMDocument);
  try
    XmlDoc.Async := False;
    XmlDoc.LoadXML(sValue);
    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);
    Node:= XmlDoc.documentElement;
    if not VarIsClear(Node) then
    begin
      Nodes := Node.childNodes;
       if not VarIsClear(Nodes) then
       begin
         lNodes:= Nodes.Length;
           for i:=0 to lNodes-1 do
            Result.Add(Nodes.Item(i).Text);
       end;
    end;
  finally
     XmlDoc:=Unassigned;
  end;
end;

function GetLanguagesForSpeak: TList<string>;
var
   XmlDoc : OleVariant;
   Node   : OleVariant;
   Nodes  : OleVariant;
   lNodes : Integer;
   i      : Integer;
   sValue : string;
begin
  Result:=TList<string>.Create;
  sValue:=WinInet_HttpGet(Format(MicrosoftTranslatorGetSpkUri,[BingAppId]));
  XmlDoc:= CreateOleObject(Msxml2_DOMDocument);
  try
    XmlDoc.Async := False;
    XmlDoc.LoadXML(sValue);
    if (XmlDoc.parseError.errorCode <> 0) then
     raise Exception.CreateFmt('Error in Xml Data %s',[XmlDoc.parseError]);
    Node:= XmlDoc.documentElement;
    if not VarIsClear(Node) then
    begin
      Nodes := Node.childNodes;
       if not VarIsClear(Nodes) then
       begin
         lNodes:= Nodes.Length;
           for i:=0 to lNodes-1 do
            Result.Add(Nodes.Item(i).Text);
       end;
    end;
  finally
     XmlDoc:=Unassigned;
  end;
end;

procedure Speak(const FileName,AText,Lng:string);
var
  Stream : TFileStream;
begin
  Stream:=TFileStream.Create(FileName,fmCreate);
  try
    WinInet_HttpGet(Format(MicrosoftTranslatorSpeakUri,[BingAppId,AText,Lng]),Stream);
  finally
     Stream.Free;
  end;
end;

var
 lng       : TList<string>;
 s         : string;
 FileName  : string;

begin
 try
    ReportMemoryLeaksOnShutdown:=True;
    CoInitialize(nil);
    try
      Writeln(TranslateText('Hello World','en','es'));
      Writeln(DetectLanguage('Hello World'));

      Writeln('Languages for translate supported');
      lng:=GetLanguagesForTranslate;
      try
        for s in lng do
         Writeln(s);
      finally
        lng.free;
      end;

      Writeln('Languages for speak supported');
      lng:=GetLanguagesForSpeak;
      try
        for s in lng do
         Writeln(s);
      finally
        lng.free;
      end;

      FileName:=ExtractFilePath(ParamStr(0))+'Demo.wav';
      Speak(FileName,'This is a demo using the Microsoft Translator Api from delphi, enjoy','en');
      ShellExecute(0, 'open', PChar(FileName),nil,nil, SW_SHOWNORMAL) ;

    finally
      CoUninitialize;
    end;
 except
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;
end.


5 Comments

Delphi IDE Theme Editor – New features added

New features was added to the Delphi IDE Theme Editor.

Added Color dialog with color picker.

Added option to report bugs and suggestions directly

The themes included with the tool were improved.

Some Minor bugs fixed

Check the screenshots

To download the last version go to the main page of the project.

Remember. to make your suggestions or report bugs use the Issue page of the project.