2015年12月28日月曜日

EGPからログを抽出するVBScript

EGPからログを抽出するスクリプトを作りました。EGPをZIPのファイルにコピーして、そのなかからresult.logのファイルを特定し、1つのファイルに出力しています。この方法は、公式ではなくNon Supportです。EGPのファイルが壊れたときに救済手段として、どこかに紹介されていました。
DebugFlag = Trueだと、メッセージのダイアログを表示します。あと、ログファイルの並び順は意識していません。

'---
' プログラム: EGPのファイルからSASログを抽出します。
'       説明: EGPの拡張子をZIPに変更して、ZIPのフォルダからresult.logのファイルを抽出
'     作成者: mining
'
'      引数1: EGPのファイルパス
'      引数2: ログファイルのパス
'     実行例: cscript foo.vbs C:\temp\foo.egp C:\temp\foo.log
'
'---

Option Explicit
On Error Resume Next

'---
' 定数
'---

Const FOF_SILENT = &H4              ' 進捗ダイアログを表示しない
Const FOF_NOCONFIRMATION = &H10     ' 上書き確認ダイアログを表示しない
Const ForWriting = 2                ' テキストファイルのオープン
Const ForReading = 1                ' テキストファイルのオープン
Const TristateUseDefault = -2       ' Opens the file using the system default.
Const TristateTrue = -1             ' Opens the file as Unicode.
Const TristateFalse = 0             ' Opens the file as ASCII.
Const DebugFlag = True

'---
' 変数
'---

Dim objShell
Dim objFso
Dim objTs
Dim sZipFile
Dim sTempFolder
Dim sEgpFilePath
Dim sLogFilePath
Dim ErrCount
Dim WarCount
Dim sMsg

'---
' オブジェクト生成します。
'---

Set objShell = CreateObject("Shell.Application")
Set objFso = CreateObject("Scripting.FileSystemObject")

'---
' 引数をチェックします。
'--

If WScript.Arguments.Count <> 2 Then
    Call MsgBox("引数1にEGP、引数2にログファイルを指定してください。", vbOKOnly + vbExclamation, WScript.ScriptName)
    WScript.Quit(1)
End If

sEgpFilePath = WScript.Arguments.Item(0)
sLogFilePath = WScript.Arguments.Item(1)

If objFso.FileExists(sEgpFilePath) = False Then
    Call MsgBox("引数1で指定したファイルが存在しません。", vbOKOnly + vbExclamation, WScript.ScriptName)
    WScript.Quit(1)
End If

'---
'   デバッグ用のメッセージ出力
'---

Sub DebugMsg(msg)
    If DebugFlag = True Then
        Call MsgBox(msg, vbOKOnly + vbInformation, WScript.ScriptName)
    End If
End Sub


'---
'   ZIPファイルを指定したフォルダに解凍
'---

Sub Unzip(objShell, sFile, sFolder)
    Dim objFilesInZip
    Dim objFolder
    
    Set objFilesInZip = objShell.Namespace(sFile).Items
    If Err.Number <> 0 Then
        Exit Sub
    End If
    Set objFolder = objShell.Namespace(sFolder)
    If Err.Number <> 0 Then
        Exit Sub
    End If
    
    If (Not objFolder Is Nothing) Then
        objFolder.CopyHere objFilesInZip, FOF_NOCONFIRMATION + FOF_SILENT
    Else
 Err.Raise 432 ' オートメーションの操作中にファイル名またはクラス名を見つけられませんでした。
    End If

   Set objFilesInZip = Nothing
   Set objFolder = Nothing
End Sub

'---
'   フォルダを作成
'---

Sub CreateUnzipFolder(objFso, sFolder)
    objFso.CreateFolder sFolder
End Sub

'---
'   フォルダを削除
'---

Sub DeleteUnzipFolder(objFso, sFolder)
    If objFso.FolderExists(sFolder) = True Then
        objFso.DeleteFolder sFolder, True
    End If
End Sub

'---
'   テンポラリのフォルダのパスを作成
'---

Function CreateFolderPath(objFso, sFolder)
    Const TemporaryFolder = 2
    Dim objTempFolder
    
    Set objTempFolder = objFso.GetSpecialFolder(TemporaryFolder)
    CreateFolderPath = objFso.BuildPath(objTempFolder.Path, sFolder)
    Set objTempFolder = Nothing
End Function

'---
'   サブフォルダからresult.logを探して、objTsに出力
'---

Sub SearchLog(objFso, objTs, tmpFolderItems)
    Const FileName = "result.log"
    Dim objFolderItemsB
    Dim objItem
    Dim Stream
    
    For Each objItem in tmpFolderItems
    
        ' 取り出した物がファイルかフォルダかを判定
        If objItem.IsFolder Then
            ' フォルダであれば、再帰呼び出しでフォルダ階層を手繰ります。
            Set objFolderItemsB = objItem.GetFolder
            Call SearchLog(objFso, objTs, objFolderItemsB.Items())
        ElseIf objItem.Name = FileName Then
            ' ファイル名が一致したら、テキストを読み取りobjTSに出力します。
            Set Stream = CreateObject("ADODB.Stream")
            Stream.Charset = "UTF-8"
            Stream.Type = 2
            Stream.Open
            Stream.LoadFromFile(objItem.Path)
            objTs.Write(Stream.ReadText)
            Stream.Close
            Set Stream = Nothing
        End If
    
    Next
    
    Set objItem = Nothing
    Set objFolderItemsB = Nothing

End Sub

'---
'   ログファイルからERROR, WARNINGの件数をカウント
'---

Sub CountLog(objFso, sLogFile, byRef ErrCount, byRef WarCount)
    Const KeyError = "e ERROR"
    Const KeyWarning = "w WARNING"
    Dim objTs
    Dim sBuf

    On Error Goto 0

    ErrCount = 0
    WarCount = 0

    Set objTs = objFso.OpenTextFile(sLogFile, ForReading, False, TristateTrue)
    If Err.Number <> 0 Then
        Exit Sub
    End If

    Do Until objTs.AtEndOfLine = True
        sBuf = objTs.ReadLine
        If Left(sBuf, Len(KeyError)) = KeyError Then
            ErrCount = ErrCount + 1
        ElseIf Left(sBuf, Len(KeyWarning)) = KeyWarning Then
            WarCount = WarCount + 1
        End If
    Loop

    objTs.Close
    If Err.Number <> 0 Then
        Exit Sub
    End If

    Set objTs = Nothing

End Sub

'---
' エラーチェック
'---

Function CheckError(fnName)
    Checkerror = False
    
    Dim strmsg
    Dim errNum
    
    If Err.Number <> 0 Then
        strmsg = "Error #" & Hex(Err.Number) & vbCrLf & "In Function " & fnName & vbCrLf & Err.Description
        Call MsgBox(strmsg, vbOkOnly + vbCritical, WScript.ScriptName)
        Checkerror = True
    End If
         
End Function

'---
' ログファイルを開きます。
'---

Set objTs = objFso.CreateTextFile(sLogFilePath, True, True)
If CheckError("objFso.CreateTextFile") Then
    WScript.Quit(1)
End If

'---
' EGPの拡張子をZIPに変更してコピーします。
'---

sZipFile = CreateFolderPath(objFso, objFso.GetTempName & ".zip")
Call DebugMsg("EGPの拡張子をZIPに変えてコピー:" & sZipFile)
objFso.CopyFile sEgpFilePath, sZipFile
If CheckError("objFso.CopyFile") Then
    WScript.Quit(1)
End If

'---
' 解凍先のテンポラリのフォルダを作成します。
'---

sTempFolder = CreateFolderPath(objFso, objFso.GetTempName)
Call DebugMsg("テンポラリのフォルダを作成:" & sTempFolder)
Call CreateUnzipFolder(objFso, sTempFolder)
If CheckError("CreateUnzipFolder") Then
    WScript.Quit(1)
End If


'---
' ZIPファイルを解凍します。
'---

Call DebugMsg("ZIPファイルを解凍:" & sZipFile)
Call Unzip(objShell, sZipFile, sTempFolder)
If CheckError("Unzip") Then
    WScript.Quit(1)
End If


'---
' テンポラリフォルダからログファイル探してobjTSに出力します。
'---

Call DebugMsg("テンポラリのフォルダからログを収集:" & sTempFolder)
Call SearchLog(objFso, objTs, (objShell.NameSpace(sTempFolder)).Items)
If CheckError("SearchLog") Then
    WScript.Quit(1)
End If

'---
' ログファイルを閉じます。
'---

objTs.Close
If CheckError("objTs.Close") Then
    WScript.Quit(1)
End If

'---
' テンポラリのフォルダを削除します。
'---

Call DebugMsg("テンポラリのフォルダを削除:" & sTempFolder)
Call DeleteUnzipFolder(objFso, sTempFolder)
If CheckError("DeleteUnzipFolder") Then
    WScript.Quit(1)
End If

'---
' ZIPファイルを削除します。
'---

Call DebugMsg("ZIPファイルを削除:" & sZipFile)
objFso.DeleteFile sZipFile
If CheckError("objFso.DeleteFile") Then
    WScript.Quit(1)
End If

'---
' Error, Warningの件数を数えます。
'---

Call CountLog(objFso, sLogFilePath, ErrCount, WarCount)
If CheckError("objFso.DeleteFile") Then
    WScript.Quit(1)
End If
Call DebugMsg("ERROR件数:" & CStr(ErrCount) & " WARNING件数:" & CStr(WarCount))

'---
' オブジェクトを破棄します。
'---

Set objTs = Nothing
Set objFso = Nothing
Set objShell = Nothing

'---
' 終了
'---

WScript.Quit(0)


2015年12月26日土曜日

転職、3ヶ月が経過

忙しさを理由に2015年9月に転職しました。
3ヶ月経過しましたが、思うところを書き留めておきます。

旅費清算、出張手配、捺印申請とかはやってみないとわからない。
イントラに書いてあるとおりやっても、大体うまくいきません。書かれていないルールに対応するのは慣れしかないと諦めます。

査定分の収入がしばらく入らず減収です。給与はベースとインセンティブの部分で分かれていますが、評価を受けるまではベースのみです。来年4月までは評価部分がゼロです。

3ヶ月間はNon Billableということで、順応期間でした。が、5週目から客先に単独で放り込まれましたが、なんとかなっています。亀の甲より、年の功。持っているスキルがお客様に合ってよかったです。

記録まで

EGPからログを取り出す方法

SAS Enterprise Guide Projectファイルからログを取り出す方法を調べました。調べてみるといくつか技術的な課題あり、てこずったのでここに記しておきます。EGのバージョンは7.1です。さて、その方法ですが、4種類みつけました。

  1. OLE AutomationでVBScriptやPowerShellから取り出す
  2. EGのツール、オプションでカスタムコードを書いてログの出力先を変える
  3. EGPの拡張子をZIPに変えて、フォルダの中からログを取り出す
  4. EGのツール、オプション、Application Loggingでログを出力する


OLE AutomationからVBSやPowerShellでログを取り出す

この方法は、スケジューリングで出力されるVBSのファイルを開いて、手を加えればできます。しかしながら、クエリ、コードタスク以外のログが取れませんでした。ProjectItemsの中をループで探りましたがランクや転置などデータ加工のタスク、グラフ作成のタスクが捕捉できませんでした。何故かは分かっていませんが、そういう実装なのだと割り切って考えています。

EGのツール、オプションでカスタムコードを書いてログの出力先を変える

proc printtoでログの出力先を変えます。この方法が使えるかと思いきや、クライアント/サーバ構成だとログがサーバ上に出力されて、クライアントPCから参照するためにもう一工夫必要になりあます。サーバ側でログの加工をするのであれば、問題ありません。カスタムのコードを設定する箇所が複数ありますが、使い分けは調べてください。

EGPの拡張子をZIPに変えて、フォルダの中からログを取り出す

元ネタはココの記事です。試してみると、サブフォルダのなかにresult.logがありますので、これをピックアップすればログを抽出できます。サブフォルダの中を見ると、削除されたと思しきタスクの殻フォルダがいくつかあります。ゴミがあるやも知れないので、この方法を使うときには検証をしてください。Non Supportです。

Tools、Options、Application Loggingでログを出力する

元ネタはココです。カスタムコードでproc printtoと似ていますが、サーバが出力するコード以外のログが含まれます。


EGPからログを取り出すときの検討項目を挙げます。

  • プロジェクトファイル名を取得するか否か
  • ログは時系列で取り出す必要があるか
  • SASログ以外のログが含まれても良いか
  • ログの出力先はローカルのPC、又はサーバ上?
  • 抽出するファイルは1本にまとめるか、複数バラバラで良いか?

参考まで

2015年9月11日金曜日

SAS Log Inspector 1.3.1.1 リリース


機能は変えず、組み込みのマッチングパターンを23種類に増やしました。どんなパターンが定義されているかは、以下の画像をご覧ください。久々の更新で、Vectorに登録申請しました。



SAS Log Inspectorの機能

  • 複数のログファイルからエラー、バグに繋がるNOTEを集計
  • 日本語と英語版のメッセージに対応
  • Excelファイルにログをチェックした結果をエクスポート
  • 任意のチェック項目を検査対象から外すことができる
  • Notepadを起動して、該当のログファイルを参照できる
  • マッチングパターンを画面から追加、修正できる

2015年9月8日火曜日

転職

ひどく忙しい期間が続き、仕事の方向性が発散してきたので、転職しました。
これからも、SASに絡んだ仕事を続けます。
滞っているSAS Logのツールも新しくししたいです。