之前寫過一篇WinCC的電子簽名與審計追蹤,在那篇文章中使用報警操作記錄生成審計追蹤,后來測試VB腳本執行的情況,發現審計追蹤中缺少執行該操作的用戶名和計算機名,用C腳本執行倒是沒有問題。在本文中再補充一個用InserAuditEntryNew生成審計追蹤的方法,並且不再把電子簽名和審計追蹤做在一個函數里,將電子簽名和審計追蹤分成兩個函數分別執行可以更靈活。
目錄
生成審計追蹤的方法
用腳本向Audit中添加記錄有兩種方法,一種方法是用WinCC提供的InserAuditEntryNew函數寫入,另一種方法是生成屬於“操作員輸入消息”類型的報警消息,該報警消息同時也會記錄到Audit中。
WinCC中的某些操作本身就會生成審計追蹤記錄,例如啟動或關閉系統、登錄用戶等,這些操作生成的Audit記錄的TargetName列是操作內容,而使用InserAuditEntryNew函數生成的記錄在TargetName列的內容默認為VBScripting Runtime或CScripting Runtime,而操作內容只能記錄在Reason列處。
在WinCC的電子簽名與審計追蹤一文中介紹了如何用“操作員輸入消息”類型的報警生成把操作內容記錄在TargetName列的審計追蹤消息,之前以C腳本測試過這個方法可以達到要求,后來用VB腳本測試,審計追蹤中ApplicationUser和ComputerName中是空的,即沒有操作人和操作終端名稱。如果要用VB腳本執行,這種方法是不能滿足審計追蹤要求的。在本文中再介紹使用InserAuditEntryNew函數生成審計追蹤的方法。
VB腳本的電子簽名和審計追蹤
VB腳本的InsertAuditEntryNew函數
函數原型:
InsertAuditEntryNew(strOldValue, strNewValue, strOpComments, iComment)
參數:
參數 | 描述 |
strOldValue | 舊值 |
strNewValue | 新值 |
strOpComments | 注釋 |
iComment | 0:將strOpComments參數的值作為注釋記錄到audit trial中 1:顯示一個對話框,將對話框中輸入的內容的作為注釋記錄到audit trial中 |
返回值:
- 整型值,含義未知,沒有找到對此說明的官方文檔。
VB腳本的審計追蹤
把InserAuditEntryNew函數再封裝為CreateOpMsg,添加一些參數。在VB全局腳本的項目模塊中建立該函數。
函數原型:
Function CreateOpMsg(sSource, sInputMsg, OldValue, NewValue, sComments)
代碼:
'---------------------------------------------------------------------------- ' 描述: ' 該函數向audit數據庫中插入一條審計追蹤記錄, ' 在數據庫的reason列寫入"sSource: sInputMsg; sComments"字符串, ' 由三個字符串參數拼接而成。 ' 參數: ' sSource :該字符串表示這條記錄的來源 ' sInputMsg :該字符串表示這條記錄的操作說明 ' OldValue :舊值 ' NewValue :新值 ' sComments :用戶注釋 ' 返回值: ' 與InsertAuditEntryNew函數返回值相同。 '---------------------------------------------------------------------------- Function CreateOpMsg(sSource, sInputMsg, OldValue, NewValue, sComments) CreateOpMsg = InsertAuditEntryNew(CStr(OldValue), CStr(NewValue), sSource&": "&sInputMsg&"; "&sComments, 0) '-------------------------------- ' 以下為通過報警生成審計追蹤的代碼 '-------------------------------- ' '創建操作員消息 ' dim myAlarm ' Set myAlarm = HMIRuntime.Alarms(12508142) ' MyAlarm.State = 5 ' '-------------------------- ' 'State Alarm Log Status ' '1 Came In ' '2 Went Out ' '5 Came in and comment ' '6 Gone and comment ' '-------------------------- ' myAlarm.Comment = sComments ' myAlarm.UserName = HMIRuntime.Tags("@NOP::@CurrentUser").Read ' myAlarm.ProcessValues(2) = OldValue ' myAlarm.ProcessValues(3) = NewValue ' myAlarm.ProcessValues(10) = sSource ' myAlarm.ProcessValues(7) = sInputMsg ' MyAlarm.Create End Function
VB腳本的電子簽名
在VB全局腳本的項目模塊中建立以下函數。
函數原型:
Function EsigDialog(Byref sComments, Byval bForcecomment, Byval sUserName)
代碼:
'----------------------------------------------------------------------------------------- ' 描述: ' 執行該函數將彈出電子簽名對話框, ' 並要求輸入注釋,可以設置是否要求強制注釋, ' 注釋內容通過sComments參數返回, ' 如果未給sUserName參數寫入字符串,則使用當前登錄的賬戶執行電子簽名, ' 如果給sUserName參數寫入了字符串,則將該字符串作為用戶名執行電子簽名。 ' 參數: ' sComments :引用一個字符串變量,注釋內容將會寫入到該變量中。 ' bForcecomment : ' False:可以不輸入注釋,即注釋為空; ' True :必須輸入注釋。 ' sUserName :執行電子簽名的用戶名,如果該參數為空字符串,則使用當前登錄的賬戶執行電子簽名。 ' 返回值: ' -1:函數執行遇到錯誤 ' 1:電子簽名通過 ' 2:用戶取消了電子簽名 ' 3:三次電子簽名未通過 '------------------------------------------------------------------------------------------ Function EsigDialog(Byref sComments, Byval bForcecomment, Byval sUserName) Dim sDisplayedUser, sDomain sDomain = "" '如果加入了域,在此填寫域名 '如果sUserName參數不為空,則使用提供的用戶名做電子簽名,否則使用當前登錄的賬戶做電子簽名 sUserName = Trim(sUserName) if sUserName <> "" then sDisplayedUser = sUserName Else sUserName = HMIRuntime.Tags("@NOP::@CurrentUser").Read sDisplayedUser = HMIRuntime.Tags("@NOP::@CurrentUserName").Read If sUserName = "" Then Select Case HMIRuntime.Language Case 2052 Msgbox "用戶未登錄,請登錄后再執行!" Case 1033 Msgbox "Please log in before executing!" Case Else Msgbox "Please log in before executing!" End Select EsigDialog = -1 Exit Function End If End If '電子簽名 Dim myEsig Dim ret Set myEsig = CreateObject("CCEsigDlg.ESIG") '強制注釋 myEsig.forcecomment = bForcecomment ret = myEsig.showDialog(sUserName,sDisplayedUser,sDomain, HMIRuntime.Language, sComments) EsigDialog = ret End Function
VB腳本的電子簽名和審計追蹤示例
'--------------------------------------------------------------- ' 設定一個變量的新值,完成電子簽名后,將修改的值記錄到審計追蹤 '---------------------------------------------------------------' Dim OldValue, NewValue Dim sComments NewValue = inputbox("設定加熱溫度") if Not IsNumeric(NewValue) then msgbox ("只能輸入數字!") else if EsigDialog(sComments, False, "") = 1 then OldValue = HMIRuntime.Tags("tag1").Read HMIRuntime.Tags("tag1").Write NewValue Call CreateOpMsg("<設備名稱>", "設定加熱溫度" , OldValue, NewValue, sComments) end if end if
VB腳本的寫入變量新值的封裝函數
在通過電子簽名后,向一個變量寫入新值,並記錄到審計追蹤,這個操作在WinCC中經常會使用。可以看到前面的代碼稍有復雜,對這個過程再做一次封裝在使用時會更加方便。
函數原型:
Function TagNewValueES(sSource, sInputMsg, sTagName, NewValue)
代碼:
'------------------------------------------------------------------------------------ ' 描述: ' 執行電子簽名,通過后向一個變量中寫入新值,並將變量的新值和舊值記錄到審計追蹤, ' 在審計追蹤的reason列中寫入"sSource: sTagName: sInputMsg; sComments"字符串。 ' 參數: ' sSource :該字符串表示這條記錄的來源 ' sInputMsg :該字符串表示修改變量的操作說明 ' sTagName :變量名 ' NewValue :新值 ' 返回值: ' -1:函數執行遇到錯誤 ' 1:電子簽名通過 ' 2:用戶取消了電子簽名 ' 3:三次電子簽名未通過 '-------------------------------------------------------------------------------------- Function TagNewValueES(sSource, sInputMsg, sTagName, NewValue) Dim OldValue Dim sComments Dim iRet iRet = EsigDialog(sComments, False, "") if iRet = 1 then OldValue = HMIRuntime.Tags( sTagName ).Read HMIRuntime.Tags( sTagName ).Write NewValue Call CreateOpMsg(sSource, sTagName&": "&sInputMsg , OldValue, NewValue, sComments) end if TagNewValueES = iRet End Function
C腳本的電子簽名和審計追蹤
C腳本的InsertAuditEntryNew函數
函數原型:
long int InsertAuditEntryNew(char* szOldValue, char* szNewValue, char* szOpComments, BOOL iComment, char* szReturnBuffer)
參數:
參數 | 描述 |
szOldValue | 舊值 |
szNewValue | 新值 |
szOpComments | 注釋 |
iComment | 0:將strOpComments參數的值作為注釋記錄到audit trial中 1:顯示一個對話框,將對話框中輸入的內容的作為注釋記錄到audit trial中 |
szReturnBuffer | 引用一個字符串數組,含義不明,官方文檔未對此說明 |
返回值:
- 整型值,含義未知,沒有找到對此說明的官方文檔。
C腳本的審計追蹤
把InserAuditEntryNew函數再封裝為CreateOpMsg,添加一些參數。在C全局腳本的項目函數中建立該函數。
函數原型:
long int CreateOpMsg(char* szSource, char* szMsg, char* szOldValue, char* szNewValue, char *szComments)
代碼:
#include "apdefap.h" /*--------------------------------------------------------------------------------------------------- * 描述: * 該函數向audit數據庫中插入一條審計追蹤記錄, * 在數據庫的reason列寫入"szSource: szMsg; szComments"字符串, * 由三個字符串參數拼接而成。 * 參數: * szSource :該字符串表示這條記錄的來源 * szMsg :該字符串表示這條記錄的操作說明 * szOldValue :舊值 * szNewValue :新值 * szComments :用戶注釋 * 返回值: * 與InsertAuditEntryNew函數返回值相同。 *----------------------------------------------------------------------------------------------------*/ long int CreateOpMsg(char* szSource, char* szMsg, char* szOldValue, char* szNewValue, char *szComments) { char szCommentsBuffer[1024] = ""; char szReturnBuffer[1024] = ""; sprintf(szCommentsBuffer,"%s: %s; %s", szSource, szMsg, szComments); return InsertAuditEntryNew(szOldValue,szNewValue,szCommentsBuffer,0,szReturnBuffer); //Return-Type: long int }
C腳本的電子簽名
在C全局腳本的項目函數中建立以下函數。
函數原型:
int EsigDialog(char* szComments,BOOL bForcecomment, char* szUserName)
代碼:
#include "apdefap.h" /*--------------------------------------------------------------------------------------------- * 描述: * 執行該函數將彈出電子簽名對話框, * 並要求輸入注釋,可以設置是否要求強制注釋, * 注釋內容通過szComments參數返回, * 如果未給szUserName參數寫入字符串,則使用當前登錄的賬戶執行電子簽名, * 如果給szUserName參數寫入了字符串,則將該字符串作為用戶名執行電子簽名。 * 參數: * szComments :引用一個字符串變量,注釋內容將會寫入到該變量中。 * bForcecomment : * False:可以不輸入注釋,即注釋為空; * True :必須輸入注釋。 * szUserName :執行電子簽名的用戶名,如果該參數為空字符串,則使用當前登錄的賬戶執行電子簽名。 * 返回值: * -1:函數執行遇到錯誤 * 1:電子簽名通過 * 2:用戶取消了電子簽名 * 3:三次電子簽名未通過 *-----------------------------------------------------------------------------------------------*/ int EsigDialog(char* szComments,BOOL bForcecomment, char* szUserName) { char *Domain = ""; //如果加入了域,在此填寫域名 char *szDisplayedUser = ""; int iRet = 0; VARIANT vtComment; __object* EsigDlg; char szUserNameBuffer[256]=""; char *strBegin = NULL; char *strEnd = NULL; /* 刪除szUserName字符串兩端空格 */ if (szUserName != NULL ){ strBegin = szUserName; while(*strBegin && isspace(*strBegin)) strBegin++; //如果是空格,首地址往前移一位,如果不是,則跳過該循環 strncpy(szUserNameBuffer,strBegin,255); strEnd = szUserNameBuffer + (strlen(szUserNameBuffer) - 1); while(*strEnd && isspace(*strEnd)) *strEnd-- = '\0'; //如果是空格,末地址往前移一位,並賦結束符 } /* 如果szUserNameBuffer參數不為空,則使用提供的用戶名做電子簽名,否則使用當前登錄的賬戶做電子簽名 */ if (strlen(szUserNameBuffer)>0) { szUserName = szUserNameBuffer; szDisplayedUser = szUserName; } else { szUserName = GetTagChar("@NOP::@CurrentUser"); //Return-Type: char* szDisplayedUser = GetTagChar("@NOP::@CurrentUserName"); //Return-Type: char* /* 判斷用戶是否登錄 */ if (strlen(szUserName) == 0) { switch (GetLanguage()) { case 2052: MessageBox(NULL,"用戶未登錄,請登錄后再執行!","Error",MB_SYSTEMMODAL|MB_OK); break; case 1033: default: MessageBox(NULL,"Please log in before executing!","Error",MB_SYSTEMMODAL|MB_OK); break; } return -1; } } /* 電子簽名 */ EsigDlg = __object_create("CCESigDlg.ESIG"); if (!EsigDlg) { printf("Failed to create Picture Object\n"); return -1; } EsigDlg->forcecomment = bForcecomment; iRet = EsigDlg->ShowDialog(szUserName,szDisplayedUser,Domain,GetLanguage(),&vtComment); __object_delete(EsigDlg); /* 提取注釋 */ if (szComments != NULL) sprintf(szComments,"%ls",vtComment.u.bstrVal); VariantClear(&vtComment); return iRet; }
C腳本的電子簽名和審計追蹤示例
#include "apdefap.h" void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName) { // WINCC:TAGNAME_SECTION_START // syntax: #define TagNameInAction "DMTagName" // next TagID : 1 // WINCC:TAGNAME_SECTION_END // WINCC:PICNAME_SECTION_START // syntax: #define PicNameInAction "PictureName" // next PicID : 1 // WINCC:PICNAME_SECTION_END /*------------------------------------------------------------------ * 設定一個變量的新值,完成電子簽名后,將修改的值記錄到審計追蹤 *------------------------------------------------------------------*/ char szComments[2014]=""; double OldValue, NewValue; char sOldValue[512]=""; char sNewValue[512]=""; NewValue = 97; if (EsigDialog (szComments, FALSE, "") == 1 ) { OldValue = GetTagDouble("tag1"); SetTagDouble("tag1", NewValue); sprintf(sOldValue,"%f",OldValue); sprintf(sNewValue,"%f",NewValue); CreateOpMsg("<設備名稱>", "設定加熱溫度", sOldValue, sNewValue, szComments); } }
C腳本的寫入變量新值的封裝函數
在通過電子簽名后,向一個變量寫入新值,並記錄到審計追蹤,這個操作在WinCC中經常會使用。可以看到前面的代碼稍有復雜,對這個過程再做一次封裝在使用時會更加方便。
向數值類型的變量寫入新值
函數原型:
int DoubleTagNewValueES(char* szSource, char* szMsg, char* szTagName, double dNewValue)
代碼:
#include "apdefap.h" /*--------------------------------------------------------------------------------------- * 描述: * 執行電子簽名,通過后向一個變量中寫入新值,並將變量的新值和舊值記錄到審計追蹤, * 在審計追蹤的reason列中寫入"szSource: szTagName: szMsg; szComments"字符串。 * 參數: * szSource :該字符串表示這條記錄的來源或類型 * szMsg :該字符串表示修改變量的操作說明 * szTagName :變量名 * dNewValue :新值 * 返回值: * -1:函數執行遇到錯誤 * 1:電子簽名通過 * 2:用戶取消了電子簽名 * 3:三次電子簽名未通過 *-----------------------------------------------------------------------------------------*/ int DoubleTagNewValueES(char* szSource, char* szMsg, char* szTagName, double dNewValue) { char szComments[2014]=""; double dOldValue; char szOldValue[512]=""; char szNewValue[512]=""; char szbuffer[512]=""; int iRet = EsigDialog (szComments, FALSE, ""); if ( iRet == 1 ) { dOldValue = GetTagDouble(szTagName); SetTagDouble(szTagName, dNewValue); sprintf(szOldValue,"%f",dOldValue); sprintf(szNewValue,"%f",dNewValue); sprintf(szbuffer,"%s: %s", szTagName, szMsg); CreateOpMsg(szSource, szbuffer, szOldValue, szNewValue, szComments); } return iRet; }
向字符串類型的變量寫入新值
函數原型:
int StrTagNewValueES(char* szSource, char* szMsg, char* szTagName, char* szNewValue)
代碼:
#include "apdefap.h" /*------------------------------------------------------------------------------------- * 描述: * 執行電子簽名,通過后向一個變量中寫入新值,並將變量的新值和舊值記錄到審計追蹤, * 在審計追蹤的reason列中寫入"szSource: szTagName: szMsg; szComments"字符串。 * 參數: * szSource :該字符串表示這條記錄的來源或類型 * szMsg :該字符串表示修改變量的操作說明 * szTagName :變量名 * szNewValue :新值 * 返回值: * -1:函數執行遇到錯誤 * 1:電子簽名通過 * 2:用戶取消了電子簽名 * 3:三次電子簽名未通過 *--------------------------------------------------------------------------------------*/ int StrTagNewValueES(char* szSource, char* szMsg, char* szTagName, char* szNewValue) { char szComments[2014]=""; char szOldValue[512]=""; char szbuffer[512]=""; int iRet = EsigDialog (szComments, FALSE ,""); if (iRet == 1 ) { sprintf(szOldValue,"%s", GetTagChar(szTagName) ); SetTagChar(szTagName, szNewValue); sprintf(szbuffer,"%s: %s", szTagName, szMsg); CreateOpMsg(szSource, szbuffer, szOldValue, szNewValue, szComments); } return iRet; }
查看Audit記錄
WinCC提供了AuditViewer控件查看Audit記錄,該控件顯示的示例如下。
AuditViewer控件會顯示Audit數據庫中的所有列,不能通過配置隱藏某些不需要的列。下面提供一種參考方法,通過代碼查詢Audit數據顯示到表格控件中。
該方法是用VBS查詢數據庫,把查詢到的數據顯示到MSHFlexGrid控件,控件名為Grid。查詢按鈕中的代碼見下方。
Sub OnClick(Byval Item) '獲取篩選時間 Dim timeFilter Dim BeginTime, EndTime BeginTime=DateValue(ScreenItems("BeginDate").value)+TimeValue(ScreenItems("Begintime").value) EndTime=DateValue(ScreenItems("EndDate").value)+TimeValue(ScreenItems("Endtime").value) If Datediff("s",BeginTime,EndTime) <= 0 Then Msgbox TranslateText("時間選擇不正確!", 2052) Exit Sub End If timeFilter = " Dateadd(hh,[TimeZoneOffset],[DateTime])>= '"&BeginTime& ".000' AND Dateadd(hh,[TimeZoneOffset],[DateTime])<= '"&EndTime&".000' " timeFilter = Replace(timeFilter,"/","-") Dim cnn, rs,cnnStr, SQL, i Set cnn = CreateObject("ADODB.Connection") Set rs = CreateObject("ADODB.Recordset") '查詢Audit的數據庫名稱 Dim DatasourceNameRT, DatasourceNameRC, auditDataBase DatasourceNameRT = HMIRuntime.Tags("@NOP::@DatasourceNameRT").Read DatasourceNameRC = Left(DatasourceNameRT, Len(DatasourceNameRT)-1) cnnStr = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog="&DatasourceNameRC&";Data Source=" & HMIRuntime.Tags("@NOP::@ServerName").Read & "\WINCC" cnn.ConnectionString = cnnStr cnn.CursorLocation = 3 cnn.Open SQL = "select DBName from [CC_Audit_SelectionsRT]" rs.open SQL, cnn, 1, 3 If rs.EOF Then Msgbox TranslateText("Audit數據庫不存在!", 2052) Exit Sub Else auditDataBase = rs("DBName") End If rs.close cnn.Close '查詢Audit數據庫 cnnStr = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog="&auditDataBase&";Data Source=" & HMIRuntime.Tags("@NOP::@ServerName").Read & "\WINCC" cnn.ConnectionString = cnnStr cnn.CursorLocation = 3 cnn.Open Dim table,EOF_flag Dim GridControl Set GridControl = ScreenItems("Grid") table = "CL_TRAILDATA" 'SQL = "select SWITCHOFFSET([DateTime],[TimeZoneOffset]*60) as DateTime,[ApplicationUser],[ComputerName],[TargetName],[OldValue],[NewValue],[Reason] from "&table&" where "&timeFilter SQL = "select Dateadd(hh,[TimeZoneOffset],[DateTime]) as DateTime,[ApplicationUser],[ComputerName],[TargetName],[OldValue],[NewValue],[Reason] from "&table&" where "&timeFilter & "ORDER BY DateTime DESC" 'HMIRuntime.Trace SQL&vbNewline rs.open SQL, cnn, 1, 3 EOF_flag = rs.EOF GridControl.Clear GridControl.Recordset = rs For i=1 To rs.RecordCount GridControl.TextMatrix(i,0) = i Next rs.close If Not EOF_flag Then GridControl.ColWidth(1) = 3000 GridControl.ColWidth(2) = 1500 GridControl.ColWidth(3) = 2000 GridControl.ColWidth(4) = 5000 GridControl.ColWidth(5) = 4000 GridControl.ColWidth(6) = 4000 GridControl.ColWidth(7) = 8000 End If cnn.Close End Sub