bl.OGware

infrequent grumblings of a software engineer and then some… (also some Delphi programming)

Archive for the ‘Tips and Tricks’ Category

Interface unit for sending debug messages to SysInternals ProcessMonitor

Posted by tier777 on 2010-04-15

The latest version of SysInternals‘ excellent ProcessMonitor is now able to receive custom debug log messages and display them right in between the I/O logs.

Even though I still don’t quite get the rationale for the need to create a new API for this (in contrast to simply merging in the functionality of DebugView) I went straight ahead and converted the API to Delphi.

This should work with Delphi 6 and later. I successfully tested with Delphi 2007, 2009 and 2010.

(Update: the originally posted version did actually not work with Delphi 2007 or earlier because I had inexplicably overlooked “that Unicode thing”… Thanks to GunSmoker for pointing that out! Using $ifdef UNICODE and WideString is a cheap cop-out, I know – but it works)

Enjoy:

unit ProcMonDebugOutput;

interface

uses
  Windows;

type
  TProcessMonitorLogger = class
  private
    const
//      FILE_WRITE_ACCESS           = $00000002;
//      METHOD_BUFFERED             = $00000000;
//      FILE_DEVICE_PROCMON_LOG     = $00009535;
//      IOCTL_EXTERNAL_LOG_DEBUGOUT = CTL_CODE(FILE_DEVICE_PROCMON_LOG,
//                                             $81,
//                                             METHOD_BUFFERED,
//                                             FILE_WRITE_ACCESS);
      IOCTL_EXTERNAL_LOG_DEBUGOUT = $95358204;
    class var
      FDevice: THandle; // = INVALID_HANDLE_VALUE
    class function Open(): THandle;
    class procedure Close();
    {$IF CompilerVersion >= 21}
    class constructor Create;
    class destructor Destroy;
    {$IFEND}
  public
    class function Output(const AOutputString: {$ifdef UNICODE}String{$else}WideString{$endif}): Boolean;
  end;
  PML = TProcessMonitorLogger;

implementation

{ TProcessMonitorLogger }

//function CTL_CODE(const ADevType, AFunc, AMethod, AAccess: Cardinal): Cardinal; inline;
//begin
//  Result := (ADevType shl 16) or (AAccess shl 14) or (AFunc shl 2) or AMethod;
//end;

{$IF CompilerVersion >= 21}
class constructor TProcessMonitorLogger.Create;
begin
  FDevice := INVALID_HANDLE_VALUE;
end;

class destructor TProcessMonitorLogger.Destroy;
begin
  Close();
end;
{$IFEND}

class procedure TProcessMonitorLogger.Close;
begin
  if INVALID_HANDLE_VALUE <> FDevice then
    begin
      CloseHandle(FDevice);
      FDevice := INVALID_HANDLE_VALUE;
    end;
end;

class function TProcessMonitorLogger.Open(): THandle;
begin
  if INVALID_HANDLE_VALUE = FDevice then
    FDevice := CreateFile('\\.\Global\ProcmonDebugLogger',
                            GENERIC_READ or GENERIC_WRITE,
                            FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
                            nil,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL,
                            0);
  Result := FDevice;
end;

class function TProcessMonitorLogger.Output(const AOutputString: {$ifdef UNICODE}String{$else}WideString{$endif}): Boolean;
var
  lProcMonHwnd: THandle;
  lInputLength: Cardinal;
  lOutputLength: Cardinal;
  lLastError: Cardinal;
begin
  Result := False;
  if AOutputString = '' then
    SetLastError(ERROR_INVALID_PARAMETER)
  else
    begin
      lProcMonHwnd := Open();
      if lProcMonHwnd <> INVALID_HANDLE_VALUE then
        begin
          lInputLength := Length(AOutputString) * SizeOf(WideChar);
          lOutputLength := 0;
          Result := DeviceIoControl(lProcMonHwnd,
                                    IOCTL_EXTERNAL_LOG_DEBUGOUT,
                                    PWideChar(AOutputString),
                                    lInputLength,
                                    nil,
                                    0,
                                    lOutputLength,
                                    nil);
          if not Result then
            begin
              lLastError := GetLastError();
              if lLastError = ERROR_INVALID_PARAMETER then
                SetLastError(ERROR_WRITE_FAULT);
            end;
        end
      else
        SetLastError(ERROR_BAD_DRIVER);
    end;
end;

{$IF CompilerVersion < 21}
initialization
  TProcessMonitorLogger.FDevice := INVALID_HANDLE_VALUE;
finalization
  TProcessMonitorLogger.Close();
{$IFEND}
end.

Usage:

uses
  ProcMonDebugOutput;
begin
  PML.Output('How hard could it be?');
end;
Advertisements

Posted in Delphi, Tips and Tricks | Tagged: , , , , , , , | 11 Comments »

Changing an Office add-in’s load behaviour in multi-user environments

Posted by tier777 on 2009-12-11

Let’s say you have bought or downloaded some third-party Office COM-addin to use on your Citrix- or Terminal Server. Many of these will by default install for all users. What if for one reason or another you only want to let a subset of your users work with that addin? Well, here’s what:

Office determines what addins to load by looking at the entries below the following two registry keys:

[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\OfficeApp\Addins]

[HKEY_CURRENT_USER\Software\Microsoft\Office\OfficeApp\Addins]

(where OfficeApp should be replaced with Outlook, Word, Excel, etc.)

The sub-entries will be named after the addin’s ProgID, e.g. “Microsoft.VbaAddinForOutlook.1” is one included by default with most versions of Office. From the name it should typically be fairly obvious which entry belongs to the particular addin that you’re after. For the purpose discussed here it is irrelevant what values these sub-entries actually contain.

As you may have guessed the entries under HKLM define which addins get loaded for all users while the entries under HKCU define the ones that should be loaded for that user only. Thus, in order to change an addin’s load behaviour from “all users” to “some users” all you have to do is essentially move the relevant entry from HKLM to HKCU, e.g. by exporting the entry into a .reg-file and then deleting it, then using notepad to change the hive to HKEY_CURRENT_USER and finally re-import that .reg-file again for the users that should use the addin (or just use a group policy object). Remember that you will have to repeat this whenever you install an update of the addin in question as that will probably rewrite the entry under HKLM.

More details about addin registration from a developer’s point of view can be found on MSDN:

Registry Entries for Application-Level Add-ins
(You can disregard the note about registration for all users being ignored in that article as that only applies to VSTO-addins, not to COM-addins)

Posted in Outlook, Tips and Tricks | Tagged: , , , , , , , | Leave a Comment »