Mam's WebSite

Mamの覚書Q&A検索


大項目:「 Delphi 」 - 中項目:「 API 」

「 Windowsが印刷ログを記録するよう設定するには。プリンターのイベントログを読むには。 」

Windowsがプリンターへの印刷ログを記録するよう設定するには。
Delphiでプリンターの印刷ログを読むプログラムを作成するには。


回答

以下レジストリを作成すると、Windows OSが印刷ログを記録するようになります。(レジストリ作成には管理者権限が必要です)
\\HKEY_LOCAL_MACHINE\\SYSTEM\CurrentControlSet\Services\EventLog\Microsoft-Windows-PrintService/Operational
上記レジストリを作成して初めて、Windowsが印刷ログを記録するようになるので、上記レジストリを作成する前のログは存在しません。
上記レジストリを作成すると、「イベント ビューアー」からも印刷ログを見ることが出来るようになります。([アプリケーションとサービスログ]⇒[Microsoft]⇒[Microsoft-Windows-PrintService/Operational])
 

以下ソースを実行してButton1をクリックすると、上記レジストリが無ければ上記レジストリを作成し、印刷ログを読みます。
Windows7以降でUACが有効の場合、実行ファイルを右クリックして管理者として実行をクリックし、昇格しないとレジストリも作成されませんし、
印刷ログも表示されません。

ソース

unit Unit1;
 
interface
 
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, WinApi.IpHlpApi, Vcl.StdCtrls, Vcl.Grids,
  Vcl.ExtCtrls, registry;
 
type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    Panel1: TPanel;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure get_event_log(SourceName:String;stl:TStringList);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;
 
  PEVENTLOGRECORD = ^EVENTLOGRECORD;
  EVENTLOGRECORD = packed record
    Length : DWORD;
    Reserved : DWORD;
    RecordNumber : DWORD;
    TimeGenerated : DWORD;
    TimeWritten : DWORD;
    EventID : DWORD;
    EventType : WORD;
    NumStrings : WORD;
    EventCategory : WORD;
    ReservedFlags : WORD;       //Reserved
    ClosingRecordNumber : DWORD;//Reserved
    StringOffset : DWORD;
    UserSidLength : DWORD;
    UserSidOffset : DWORD;
    DataLength : DWORD;
    DataOffset : DWORD;
  end;
 
  TEventLogRecord =EVENTLOGRECORD;
 
var
  Form1: TForm1;
 
const
  EVENTLOG_SEQUENTIAL_READ    = 1;
  EVENTLOG_SEEK_READ          = 2;
  EVENTLOG_FORWARDS_READ      = 4;
  EVENTLOG_BACKWARDS_READ     = 8;
 

function TimeGeneratedToDateTime(aTimeGenerated: DWORD): TDateTime;
 
implementation
 
{$R *.dfm}
 
function TimeGeneratedToDateTime(aTimeGenerated: DWORD): TDateTime;
const
  SecondPerDay = 60 * 60 * 24; //一日の秒数
begin
  Result := EncodeDate(1970,1,1) + EncodeTime(9,0,0,0);
  Result := ((Result * SecondPerDay) + aTimeGenerated) / SecondPerDay;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  stl:TStringList;
  stl2:TStringList;
  i,c:integer;
  r:Integer;
  reg:TRegistry;
  ret:boolean;
begin
 
  reg:=TRegistry.Create;
  reg.RootKey:=HKEY_LOCAL_MACHINE;
  //レジストリを作成する。このレジストリを作成すると、印刷ログを保存するようになる
  ret:=reg.CreateKey('\SYSTEM\CurrentControlSet\Services\EventLog\Microsoft-Windows-PrintService/Operational');
  reg.Free;
 
  Screen.Cursor:=crHourGlass;
  StringGrid1.Visible:=False;;
 
  stl:=TStringList.Create;
  stl2:=TStringList.Create;
  stl.Clear;
  stl2.Clear;
  stl2.QuoteChar:='"';
  stl2.StrictDelimiter:=true;
  stl2.Delimiter:=',';
 
  StringGrid1.ColCount:=30;
  StringGrid1.Rows[0].CommaText:='日時,イベントID,ソース名,コンピューター名,イベントタイプ,ユーザー名,ドメイン'
           +',セキュリティID,アカウント名,アカウントドメイン,ログオンID,オブジェクトサーバー,オブジェクト種類,オブジェクト名'
           +',ハンドルID,トランザクションID,アクセス,アクセスマスク,特権,制限されたSID数,プロセスID,プロセス名';
  r:=1;
 
  stl.Clear;
 
  get_event_log('Microsoft-Windows-PrintService/Operational',stl);
 
  for i := 0 to stl.Count-1 do
  begin
    StringGrid1.RowCount:=r+1;
    stl2.CommaText:=stl[i];
    for c := 0 to stl2.count-1 do
    begin
      StringGrid1.Cells[c,r]:=stl2[c];
    end;
    inc(r);
  end;
 
  if r>1 then
  begin
    StringGrid1.FixedRows:=1;
    StringGrid1.ColWidths[0]:=100;
    StringGrid1.ColWidths[1]:=64;
    StringGrid1.ColWidths[2]:=128;
    StringGrid1.ColWidths[3]:=64;
    StringGrid1.ColWidths[4]:=200;
    StringGrid1.ColWidths[5]:=100;
    StringGrid1.ColWidths[6]:=100;
    StringGrid1.ColWidths[7]:=256;
  end;
 
  stl2.Free;
  stl.Free;
 
  StringGrid1.Visible:=True;
  Screen.Cursor:=crDefault;
end;
 
procedure TForm1.get_event_log(SourceName: String;stl:TStringList);
const
  //最新のログから順次読み込む
  READ_FLAG= EVENTLOG_SEQUENTIAL_READ or EVENTLOG_BACKWARDS_READ;
var
  eLogHandle: THandle;
  logCount: DWORD;
  i,j: Integer;
  eventID: Integer;
  pBuf: PEventLogRecord;
  bufSize: Integer;
  readNum, readNeed: DWORD;
 
  off:Cardinal;
  dwAccountSize, dwDomainSize: DWORD;
  szAccount, szDomain: array[0..MAX_PATH] of Char;
 
  ust:String;
  dst:String;
  us:PSID;
  peUse: Cardinal;
 
  st,strn:string;
  p:PChar;
 
  gSourceName:string;
  gComputername:string;
  gTimeGenerated:TDateTime;
  gEventType:Word;
  gEventTypeSt:string;
  gString:string;
  b:TBytes;
 
  stl2:TStringList;
 
begin
  stl2:=TStringList.Create;
 
  //システムログのオープン
  eLogHandle := OpenEventLog(nil,PWideChar(SourceName));
 
  //ログが開けた場合
  if eLogHandle > 0 then
  begin
    //ログ数を取得
    if not GetNumberOfEventLogRecords(eLogHandle, logCount) then
      logCount := 0;
    //メモリ確保
    bufSize := SizeOf(TEventLogRecord);
    pBuf := AllocMem(bufSize);
 
    for i := 1 to logCount do
    begin
      {読み込み}
      if not ReadEventLog(eLogHandle, READ_FLAG,
              logCount - 1, pBuf, 1, readNum, readNeed) then
      begin
       if GetLastError = ERROR_INSUFFICIENT_BUFFER then
       begin
         //メモリの再割り当て
         bufSize := readNeed;
         ReallocMem(pBuf, bufSize);
         if not ReadEventLog(eLogHandle, READ_FLAG,
             logCount - 1, pBuf, readNeed, readNum, readNeed) then
         begin
           Break;//失敗
         end;
       end
       else
       begin
         //失敗
         Break;
       end;
      end;
 
      //イベントIDを読む
      eventID := pBuf^.EventID AND $FFFF;
      //イベントタイプ
      gEventType:=pBuf^.EventType;
      case gEventType of
        $1:gEventTypeSt:='EVENTLOG_ERROR_TYPE';
        $2:gEventTypeSt:='EVENTLOG_WARNING_TYPE';
        $4:gEventTypeSt:='EVENTLOG_INFORMATION_TYPE';
        $8:gEventTypeSt:='EVENTLOG_AUDIT_SUCCESS';
        $10:gEventTypeSt:='EVENTLOG_AUDIT_FAILURE';
        else gEventTypeSt:='EVENTLOG_UnKnown';
      end;
 
      //ドメイン、ユーザー名を取得
      ust:='';
      dst:='';
      off:=pBuf^.UserSidOffset;
      us:=@(PAnsiChar(pBuf)[off]);
      if (pBuf^.UserSidLength>0) then
      begin
        dwAccountSize:= MAX_PATH;
        dwDomainSize:= MAX_PATH;
        if (not LookupAccountSid(nil, us, szAccount, dwAccountSize,
                                 szDomain, dwDomainSize, peUse)) then
        begin end
        else
        begin
          ust:=(szAccount);
          dst:=(szDomain);
        end;
      end;
 
      //発生日時を取得
      gTimeGenerated := TimeGeneratedToDateTime(pBuf^.TimeGenerated);
 
      //ソース名を読み取る
      p:=PChar(DWORD(pBuf)+Sizeof(TEventLogRecord));
      gSourceName:=StrPas(p);
 
      //コンピューター名を読み取る
      p:=PChar(DWORD(pBuf)+Sizeof(TEventLogRecord)+length(gSourceName)*2+2);
      gComputername:=StrPas(p);
 
      stl2.Clear;
      //文字列を読み取る
      gString:='';
      st:='';
      off:=pBuf^.StringOffset;
      if pBuf^.NumStrings>0 then
      begin
        if pBuf^.NumStrings>1 then
        begin
          if pBuf^.EventID=302 then
            st:=st;
        end;
        for j := 0 to pBuf^.NumStrings-1 do
        begin
          p:=PChar(DWORD(pBuf)+off);
          st:=StrPas(p);
 
          strn:=st;
          strn:=StringReplace(strn,',',' ',[rfReplaceAll]);
          strn:=StringReplace(strn,#10,' ',[rfReplaceAll]);
          stl2.Add(strn);
          gString:=gString+strn;
 
          inc(off,length(st)*2+2);
        end;
      end;
 
      //バイナリデータを読み取る
      setlength(b,pBuf^.DataLength);
      for j := 0 to pBuf^.DataLength-1 do
      begin
        b[j]:=PByte(DWORD(pBuf)+pBuf^.DataOffset+j)^;
      end;
 
      if (pBuf^.EventID=307) or (pBuf^.EventID=310) then  //307は印刷完了を示す。310は削除されたことを示す
      begin
        stl.Add(
          FormatDateTime('yyyy/mm/dd hh:nn:ss', gTimeGenerated)+','+
          inttostr(eventID)+','+gSourceName+','+gComputername+','+
          inttostr(gEventType)+'<'+gEventTypeSt+'>'+','+
          ust+','+dst+','+stl2.CommaText
        );
      end;
    end;
 
    //メモリ開放
    FreeMem(pBuf);
    //ログを閉じる
    CloseEventLog(eLogHandle);
 
  end;
 
  stl2.Free;
 
end;
 
end.

Copyright 2019 Mam