PowerShellでは遅すぎて…
実際問題、ログからcsvに切り出すことは可能になったものの、10MBのログから200kb前後のcsvファイルを生成するのに前回のスクリプトだと約2時間かかるんですねぇ。
そりゃ、1レコードを生成するのに、ログオンIDで再度全部ログを舐めて、PCの名前とIPを拾っているわけで…。
単純に考えればO(n^2)というわけですね。
ログのローテーションというか上限を5MB(半分)にすれば、多分処理速度は1/4程度にまで下がるとは思われるけれど、120分が30分になって、ファイル数が倍なので実質1時間かかるわけで…。orz。
何が遅い?
実際、何に時間がかかっているのかを調べ始めたのだけれど、まずwhere-Objectが結構な時間を消費している。その後foreachで回す処理は激遅い。
結局、イベントログからオブジェクトとして扱っているのが、遅さの原因ぽい。
最適化しようにも、PowerShell自体の扱いに慣れていないので、とっても疲れるです。
他に良い方法は無いものだろうか?と思案し始めた時に、以前オライリーの本で見た記憶があったので、ちょいっと検索してみる。
方針変更!
perlのモジュールにW32::EventLogが有るじゃないですか。
早速、マニュアルを参考にテスト用のコードを書いてみる。
さすがに、サーバ上での作業は心配なので、VM上のWindows7にActivePerlを入れてテストすることに。
細かい仕様を決めようか、汎用的に作ろうかと迷いつつも、現状の作業をサックリ行えることを目的に作成した。約1日かけて、コーディング終了。前回までのスクリプトと同じ結果を返すようにするところで、少々苦労したわ。
なので、若干コードは汚い(perlなのでさらに汚いww)とは思うのだけれど、これが素晴らしい性能を発揮してくれた。
コードは以下全体を載せておくので、もし万が一使いたい人は、自己責任にてお願いします。
さて、実際に同じデータを食わせてみると、なっなんと、2時間かかっていた処理を1分以内に終えてくれる!!!
あぁ、これで今後のログの整理が快適になる。
1週間分、いや1ヶ月分まとめて処理だって怖くない!
あとは、コマンドラインでイベントログを引数として渡せば良い仕様にしたので、バッチでも動かせるし…。
もう少し、バグ取りなんかも必要かもしれないし、エラー時の処理なんかは、全くしていないレベルなので、暇な時にでもやろうとは思っている。
この手間は、大きな差を産んでくれる! 120倍速の処理完了〜。
use Win32::EventLog;
$Win32::EventLog::GetMessageText = 1;
$DEBUG=0;
sub make_time {
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = @_;
return sprintf("%04d/%02d/%02d %02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec);
}
sub event_hash4624{
my($strings)=@_;
my @event=('SubjectUserSid','SubjectUserName' ,'SubjectDomainName' ,'SubjectLogonId',
'TargetUserSid' ,'TargetUserName' ,'TargetDomainName' , 'TargetLogonId',
'LogonType' ,'LogonProcessName' ,'AuthenticationPackageName','WorkstationName',
'LogonGuid' ,'TransmittedServices','LmPackageName' ,'KeyLength',
'ProcessId' ,'ProcessName' ,'IpAddress' ,'IpPort');
my @list = split(/[\0]/, $strings);
my %h;
for(my $idx=0;$idx<$#event;$idx++){
$h{$event[$idx]}=$list[$idx];
}
return %h;
}
sub event_hash4663{
my($strings)=@_;
my @event=('SubjectUserSid','SubjectUserName','SubjectDomainName' ,'SubjectLogonId',
'ObjectServer' ,'ObjectType' ,'ObjectName' ,'HandleId',
'AccessList' ,'AccessMask' ,'ProcessId' ,'ProcessName');
my @list = split(/[\0]/, $strings);
my %h;
for(my $idx=0;$idx<$#event;$idx++){
$h{$event[$idx]}=$list[$idx];
}
return %h;
}
sub convert_acl{
my ($access_list)=@_;
my %acl = ('%%1537'=>"Delete" ,'%%1538'=>"Read_CONTROL" ,'%%1539'=>"Write_DAC",
'%%1540'=>"Write_OWNER" ,'%%1541'=>"Synchronize" ,'%%4416'=>"ReadData (or List Directory)",
'%%4417'=>"WriteData (or Add File)",'%%4418'=>"AppendData (or AddSubdirectory or CreatePipeInstance)",
'%%4419'=>"ReadEA" ,'%%4420'=>"WriteEA" ,'%%4421'=>"ExecuteFile",
'%%4422'=>"DeleteChild" ,'%%4423'=>"ReadAttributes",'%%4424'=>"WriteAttributes");
$access_list =~ s/^\s*(.*?)\s*$/$1/;
if($acl{$access_list}){
return $acl{$access_list};
}else{
return "Other";
}
}
sub make_db_id {
my ($eventlogname,$event_id) = @_;
my $handle = Win32::EventLog->new($eventlogname) or die "can't open event log.\n";
$handle->GetNumber($recs) or die "Can't get number of EventLog records\n";
$handle->GetOldest($base) or die "Can't get number of oldest EventLog record\n";
my $x=0;
while ($x < $recs) {
$handle->Read(EVENTLOG_FORWARDS_READ|EVENTLOG_SEEK_READ,
$base+$x,
$hashRef) or die "Can't read EventLog entry #$x\n";
if($hashRef->{EventID} eq $event_id){
%eventdata = event_hash4624($hashRef->{Strings});
$workpc = $eventdata{'WorkstationName'}?$eventdata{'WorkstationName'}:"";
$logonname = $eventdata{'TargetUserName'}?$eventdata{'TargetUserName'}:"";
$logonid = $eventdata{'TargetLogonId'};
$logonip = $eventdata{'IpAddress'}?$eventdata{'IpAddress'}:"";
if(!($logonname =~ /\$/)){
$db_id{$logonid}=$workpc . "," . $logonip;
}
}
$x++;
}
return %db_id;
}
my $archivelogname = $ARGV[0];
my %id_list = make_db_id($archivelogname,"4624");
my $csvfilename;
$csvfilename = $archivelogname;
$csvfilename =~ s/\.evtx$/\.csv/;
$csvfilename =~ s/^.+\\//;
open(FH,">$csvfilename") or die("Can't open csv file\n");
print FH "日時,ユーザ名,ドメイン名,ログオンID,コンピュータ名,IPアドレス,ファイル名,操作,アプリケーション";
my $event_id="4663";
my $handle = Win32::EventLog->new($archivelogname) or die "can't open event log.\n";
$handle->GetNumber($recs) or die "Can't get number of EventLog records\n";
$handle->GetOldest($base) or die "Can't get number of oldest EventLog record\n";
my $x=0;
while ($x < $recs) {
$handle->Read(EVENTLOG_FORWARDS_READ|EVENTLOG_SEEK_READ,
$base+$x,
$hashRef) or die "Can't read EventLog entry #$x\n";
if($hashRef->{EventID} eq $event_id){
%eventdata = event_hash4663($hashRef->{Strings});
$csv = make_time(localtime($hashRef->{'TimeGenerated'})) . ",";
$csv .= $eventdata{'SubjectUserName'} . ",";
$csv .= $eventdata{'SubjectDomainName'} . ",";
$csv .= $eventdata{'SubjectLogonId'} . ",";
if($id_list{$eventdata{'SubjectLogonId'}}){
$csv .= $id_list{$eventdata{'SubjectLogonId'}} . ",";
}else{
$csv .= ",,";
}
$csv .= $eventdata{'ObjectName'} . ",";
$csv .= &convert_acl($eventdata{'AccessList'}) . ",";
$csv .= $eventdata{'ProcessName'};
print FH $csv."\n";
}
$x++;
}
close(FH);
コメント
コメントを投稿
励みになりますので、簡単で良いので一言くださいませ。