Hi all,
although this thread is full of valuable information, it is very hard for a beginner to set up Spamassassin properly.
Espeacially setting up the event handler script in hMailServer can become very annoying if you are looking for certain features spread over two or more code examples.
Therefore I reviewed all the code snippets from this thread (and a few others) and wrote a (hopefully) well documented event handler script that is highly configurable to fit at least the needs of low volume users. It does have the following features:
- Configurable Bayesian learning
- Configurable for scanning outgoing and/or incoming mails
- Auto discarding of mails above configurable spam score
- Freely configurable for using spamc, spamassassin, sa-learn, etc.
- Scans incoming mails over SMTP or POP
- Configurable logging to hMailServers hmailserver_events.log
The script assumes you have configured your Spamassassin by following the six steps outlined by john25uk in the first message of this thread:
john25uk wrote:
INSTALLING SPAMASSASSIN
Useful sites:
1.Perl for Windows - SITE2.Perl for Windows - INSTALLER3.SpamAssassin.org - SITE4.SpamAssassin - ZIP DOWNLOAD5.NMAKE6.Really good how to install SpamAssassin tutorialStart by downloading and saving no's 2, 4, and 5 - you will need them for the spamassassin install tutorial (6)
Follow the instructions on link 6. HINTS: type the names of the perl modules exactly as shown, including lowercase and capitals. Ignore from Part X onwards for now, but download the Windows GUI in Part XI, its good. Install everything to the default directories also.
7. After that follow the installation instructions included in the script.
I hope you'll like it...

Please reply here in this forum to any issues with the script or feature recommendations. If you feel the need to edit this script, make your changes configurable and share the result to the hMailServer forum.
Thanks goes to all contributors to this thread and this forum. Without you, this script would not have been possible.
Update 2007-08-07: Fixed bug where SAHI_auto_discard_score wasn't causing the mail to be deleted.
HUGE SCRIPT INCOMING:
Code:
'-----------------------------------------------------------------------------
'Script:
' Spamassassin hMailServer Integration (SAHI)
'
'Synopsis:
' A configurable script to integrate Spamassassin into hMailServer
'
'Script version:
' 1.1
'
'Release date:
' 2007-08-07
'
'Features:
' - Configurable Bayesian learning
' - Configurable for scanning outgoing and/or incoming mails
' - Auto discarding of mails above configurable spam score
' - Freely configurable for using spamc, spamassassin, sa-learn, etc.
' - Scans incoming mails over SMTP or POP
' - Configurable logging to hMailServers hmailserver_events.log
'
'Limitations:
' - Not meant for use in high volume environments
' - No builtin batch support for Bayesian learning
' - No redirection of spam into different IMAP folder (this must be
' configured in hMailServer)
' - No general userprefs support for scanning/learning
' - No general support for checksum services such as dcc, pyzor, or razor
'
'Requirements:
' - Requires hMailServer, Perl and Spamassassin
' to be successfully installed and the pathes to Perl and
' Spamassassin to be assigned in your PATH variable;
' In the following Spamassassin stands as a synonym for
' either spamc/spamd, spamassassin or some other
' spamassassin derivate, you can configure your prefered
' Spamassassin flavour in the config part below;
' - Requires .NET to be installed - otherwise
' command output redirection will not work and render this
' script useless; tested with .NET 2.0 but other versions may
' work also
' - Requires hMailServer 4.4
'
'Installation:
' 1. Append this code to the end of your EventHandlers.vbs
' file of hMailServer
' 2. Configure the script with the variables below
' 3. Change the OnAcceptMessage handler as follows (obviously
' without the comment quotes):
'
' Sub OnDeliverMessage(oMessage)
' SAHI_processMessage oMessage
' End Sub
'
' 4. Reload your hMailServer scripts afterwards or restart hMailServer
'
'Tested with:
' hMailServer 4.4 (B270) (polling external POP accounts)
' Perl 5.8.8 (820)
' Spamassassin 3.2.2
' Windows XP (latest patch level as at time of writing; .NET framework 2.0)
'
'History:
' 2007-08-05 - 1.0 - Soeren Weber:
' Initial version inspired by the hmailserver.com forum
' discussion "HOWTO: Use SpamAssassin with hMailServer"
'
' 2007-08-07 - 1.1 - Soeren Weber:
' Fixed bugs were SAHI_auto_discard_score wasn't causing the mail to be
' deleted
'
'-----------------------------------------------------------------------------
'Start configure the script here
Const SAHI_domain_received_header = "from myserver ([127.0.0.1]) by example.com with hMailServer ;"
'Possible values:
' Any string that uniquely identifies an outgoing mail
'This string must uniquely identify any outgoing mail that is sent
'from your local domain to an arbitrary recipient by its "Received"
'mail header
Const SAHI_scan_outgoing_mails = 0
'Possible values:
' 0 - Don't check outgoing mails for spam
' 1 - Check outgoing mails for spam
'If you trust your local users you can set this option to 0 to
'skip the check of outgoing mails
Const SAHI_scan_incoming_mails = 1
'Possible values:
' 0 - Don't check incoming mails for spam
' 1 - Check incoming mails for spam
'This option should be set to 1 to activate spam checking for all
'incoming mails; Why else should you have installed Spamassassin?
Const SAHI_add_spam_check_status_header = 0
'Possible values:
' 0 - Don't add an additional header field
' 1 - Add additional mail header field
'In case this value is set to 1, an additional X-Spam-Check-Status will be
'added to the mail, signaling the result of this script; Possible header
'values are:
' Skipped - This will be set if the spam check was skipped due to
' having set SAHI_scan_outgoing_mails or SAHI_scan_incoming_mails
' respectivly to 1
' Checked - This is set if the spam check could be successfully
' applied
' Error - This is set in case the spam check was aborted due to errors
' during processing of the mail by this script
'NOTE: Due to how IMAP works, this header field as also
'Spamassassins own additional header fields will be added to all
'incoming mails unconditionally. For outgoing mails, the headers will be
'added for the mail that was sent from hMailServer to the destination server
'but not to the copy that may be stored in your "Sent" folder;
'It is recommended to better set this value to 0 in case you have set
'SAHI_use_learn_mailboxes to 1. This may negativly influence the results
'of the Bayesian filter rules if you don't add this header field to your
'Spamassasin configuration using the bayes_ignore_header parameter;
'Besides the above mentioned keyword values for this header field, some
'further information may be appended to the field
Const SAHI_auto_discard_score = 12
'Possible values:
' 0 - No auto discarding will be used
' 1 .. n - Integer spam score needed to cause the mail to be discarded
'If the spam check results are above this threshold value, the mail will
'automatically discarded by this script; Usually you should set this to
'the same value as Spamassassins config item bayes_auto_learn_threshold_spam;
'If you are uncertain about the reliability of your Spamassassin rules
'you should avoid discarding mails, so that they are still be delivered
'to your recipients; If you don't want to discard the spam but move it
'to a different IMAP folder, use hMailServer rules; See documentation and
'forum of hMailServer for how this works
Const SAHI_default_scan_command_line = "%comspec% /c spamassassin -e"
'Const SAHI_default_scan_command_line = "%comspec% /c perl -S -T -w spamassassin -e"
'Const SAHI_default_scan_command_line = "%comspec% /c spamc -E -s 512000"
'Const SAHI_default_scan_command_line = "%comspec% /c WinSpamC.exe -t 15"
'Possible values:
' Command line used to start Spamassassin
'Command line that will be used to check emails with Spamassassin; In-
'and outfile parameter will be added by this script, so avoid setting
'them
Const SAHI_alternate_scan_command_line = ""
'Possible values:
' Command line used to start Spamassassin
'Alternate command line that will be used to check emails with
'Spamassassin in case the SAHI_default_scan_command_line failed with an error;
'You can leave this empty if you don't need alternate command processing;
'In- and outfile parameter will be added by this script, so avoid setting
'them; This parameter is useful eg. if you are using spamc for the default
'and running into an error. In this case you may run Spamassassin directly
Const SAHI_use_learn_mailboxes = 1
'Possible values:
' 0 - Don't use ham/spam/forget mailboxes
' 1 - Use ham/spam/forget mailboxes
'If set to 1 this script will cause any mail that will be sent internally to
'the SAHI_learn_ham_mailbox, SAHI_learn_spam_mailbox or
'SAHI_learn_forget_mailbox to be processed by your Bayesian learning tool;
'There may be differnt ways how to feed your tool, therefore this option is
'set to 0 by default; If you use this feature, the mail that should be learned
'is expected to be attached to a forwarding mail; In case you are using
'SquirrelMail, I recommend using the spam_buttons" plugin
Const SAHI_learn_ham_mailbox = "report_ham@example.com"
'Possible values:
' Fully quallified local email address
'You only need to set this if you have SAHI_use_learn_mailboxes set to 1;
'Replace at least the <example.com> with the domain hMailServer is
'running on; You are free to change the mailbox name aswell; There must
'be a mailbox configured in hMailServer server with this name, because
'otherwise mails sent to this address will not be accepted by hMailServer.
'The hMailServer mailbox will not fill with mails if SAHI_use_learn_mailboxes
'is set to 1
Const SAHI_learn_spam_mailbox = "report_spam@example.com"
'Possible values:
' Fully quallified local email address
'You only need to set this if you have SAHI_use_learn_mailboxes set to 1;
'Replace at least the <example.com> with the domain hMailServer is
'running on; You are free to change the mailbox name aswell; There must
'be a mailbox configured in hMailServer server with this name, because
'otherwise mails sent to this address will not be accepted by hMailServer.
'The hMailServer mailbox will not fill with mails if SAHI_use_learn_mailboxes
'is set to 1
Const SAHI_learn_forget_mailbox = "report_purgative@example.com"
'Possible values:
' Fully quallified local email address
'You only need to set this if you have SAHI_use_learn_mailboxes set to 1;
'Replace at least the <example.com> with the domain hMailServer is
'running on; You are free to change the mailbox name aswell; There must
'be a mailbox configured in hMailServer server with this name, because
'otherwise mails sent to this address will not be accepted by hMailServer.
'The hMailServer mailbox will not fill with mails if SAHI_use_learn_mailboxes
'is set to 1
Const SAHI_learn_command_line = "%comspec% /c sa-learn --"
'Possible values:
' Command line used to start sa-learn
'Command line that will be used to run the Bayesian learning tool in case
'SAHI_use_learn_mailboxes is set to 1; The learn type (ham, spam,
'forget) will be appended at the end of this command during runtime; Due to
'the way how hMailServer works, it is 'not wise to use userprefs for the
'Bayesian learning tool
Const SAHI_trace_level = 4
'Possible values:
' One of the below defined SAHI trace levels
'You can adjust the amount of trace that is written to the trace file
'hmailserver_events.log by setting this value; Eg. if you set the
'variable to SAHI_trace_warning, all traces are written that are either
'emergency, alert, critical, error or warning but no traces for notice,
'information and debug; To turn tracing off, set this variable to 0;
'For production you should set this value a lesser equal of 4
'(SAHI_trace_error) or 5 (SAHI_trace_warning) especially if you are
'running high volume
'Stop configure the script here; No more changes should be necessary after
'this line; If you feel the need to edit this script, make your changes
'configurable and share the result to the hMailServer forum
'-----------------------------------------------------------------------------
'SAHI trace levels
Const SAHI_trace_none = 0
Const SAHI_trace_emergency = 1
Const SAHI_trace_alert = 2
Const SAHI_trace_critical = 3
Const SAHI_trace_error = 4
Const SAHI_trace_warning = 5
Const SAHI_trace_notice = 6
Const SAHI_trace_information = 7
Const SAHI_trace_debug = 8
Dim SAHI_trace_str(8)
SAHI_trace_str(0) = "NONE"
SAHI_trace_str(1) = "EMERGENCY"
SAHI_trace_str(2) = "ALERT"
SAHI_trace_str(3) = "CRITICAL"
SAHI_trace_str(4) = "ERROR"
SAHI_trace_str(5) = "WARNING"
SAHI_trace_str(6) = "NOTICE"
SAHI_trace_str(7) = "INFORMATION"
SAHI_trace_str(8) = "DEBUG"
Const SAHI_version = "1.1"
Dim SAHI_sh
Set SAHI_sh = CreateObject("WScript.Shell")
Dim SAHI_fso
Set SAHI_fso = CreateObject("Scripting.FileSystemObject")
Dim SAHI_trace_id
'-----------------------------------------------------------------------------
' OnAcceptMessage callback
'-----------------------------------------------------------------------------
Sub SAHI_processMessage(oMessage)
'Synopsis:
' This is the working horse to process mail to Spamassassin; note that you
' have to call this Sub by Reference for the oMessage parameter, because
' additional mail header may be added to the message; This script will set
' Result.Value to:
' 0 - ham found, spam found while it is below SAHI_auto_discard_score
' or script ran into an error (causing mail to be delivered)
' 1 - ham/spam processed by Bayesian learning tool, spam found while it is
' above SAHI_auto_discard_score (causing mail to be discarded)
SAHI_trace_id = oMessage.id & " | " & oMessage.HeaderValue("Message-ID")
SAHI_trace SAHI_trace_information, "SAHI_Version: " & SAHI_version
SAHI_trace SAHI_trace_notice, "SAHI_processMessage: From: " & oMessage.from & " To: " & oMessage.to & " Subject: " & oMessage.subject & " Filename: " & oMessage.filename
'Lets do some consistency checks first
If SAHI_fso.FileExists(oMessage.filename) = 0 Then
SAHI_trace SAHI_trace_error, "SAHI_processMessage: File " & oMessage.filename & " not found - delivering mail"
Result.Value = 0
Exit Sub
End If
Dim mail_file
mail_file = SAHI_fso.GetAbsolutePathname(oMessage.filename)
'Find out direction of this mail
Dim is_outgoing
is_outgoing = SAHI_isOutgoing(oMessage.HeaderValue("Received"))
'Learn about ham/spam/forget if configured
If is_outgoing <> 0 Then
If SAHI_use_learn_mailboxes = 0 Then
SAHI_trace SAHI_trace_debug, "SAHI_processMessage: Usage of learn mailboxes disabled in configuration"
ElseIf SAHI_learn(oMessage, mail_file) <> 0 Then
'Stop further processing if this mail was feed to Bayesian learning tool
SAHI_trace SAHI_trace_notice, "SAHI_processMessage: File " & oMessage.filename & " was feed to Bayesian learning tool - discarding mail"
Result.Value = 1
Exit Sub
End If
End If
'We may want to skip checking outgoing or incoming mails
Dim direction_str
Dim check_mails
If is_outgoing <> 0 Then
direction_str = "outgoing"
check_mails = SAHI_scan_outgoing_mails
Else
direction_str = "incoming"
check_mails = SAHI_scan_incoming_mails
End If
Dim rc
SAHI_doScan rc, oMessage, check_mails, direction_str
If rc = 0 Then
Exit Sub
End If
'Finallay lets start scanning for spam; until otherwise noticed we want to
'deliver the mail to the recipient, even if we ran into an error during
'spam check
rc = SAHI_scan(mail_file)
oMessage.RefreshContent()
If rc = 0 Then
SAHI_trace SAHI_trace_notice, "SAHI_processMessage: Mail okay - delivering mail"
Result.Value = 0
If SAHI_add_spam_check_status_header <> 0 Then
SAHI_setHeaderValue oMessage, "X-Spam-Check-Status", "Checked on " & direction_str
End If
ElseIf rc = 1 Then
If SAHI_isAboveDiscardScore(oMessage) <> 0 Then
SAHI_trace SAHI_trace_notice, "SAHI_processMessage: Mail flagged as spam - discarding mail because it is above discard score of " & SAHI_auto_discard_score
Result.Value = 1
Else
SAHI_trace SAHI_trace_notice, "SAHI_processMessage: Mail flagged as spam - delivering mail because it is below discard score of " & SAHI_auto_discard_score
Result.Value = 0
End If
If SAHI_add_spam_check_status_header <> 0 Then
SAHI_setHeaderValue oMessage, "X-Spam-Check-Status", "Checked on " & direction_str
End If
Else
SAHI_trace SAHI_trace_notice, "SAHI_processMessage: Mail processed with errors - delivering mail"
Result.Value = 0
If SAHI_add_spam_check_status_header <> 0 Then
SAHI_setHeaderValue oMessage, "X-Spam-Check-Status", "Error on " & direction_str
End If
End If
End Sub
'-----------------------------------------------------------------------------
' status
'-----------------------------------------------------------------------------
Sub SAHI_doScan(rc, oMessage, check_mails, direction_str)
'Synopsis:
' Returns whether mails should be checked for spam for outgoing or
' incoming direction
'Possible return values:
' 0 - mail shouldn't be checked for spam
' 1 - mail should be checked for spam
rc = 1
If check_mails <> 0 Then
SAHI_trace SAHI_trace_debug, "SAHI_doScan: Spam check enabled on " & direction_str & " mails"
Else
SAHI_trace SAHI_trace_notice, "SAHI_doScan: Spam check skipped on " & direction_str & " mails - delivering mail"
Result.Value = 0
If SAHI_add_spam_check_status_header <> 0 Then
SAHI_setHeaderValue oMessage, "X-Spam-Check-Status", "Skipped on " & direction_str
End If
rc = 0
End If
End Sub
Function SAHI_isOutgoing(received)
'Synopsis:
' Checks Received mail header to decide whether this mail is outgoing or
' incoming
'Possible return values:
' 0 - mail is incoming
' 1 - mail is outgoing
SAHI_trace SAHI_trace_debug, "SAHI_isOutgoing: Received: '" & received & "'"
If InStr(1, received, SAHI_domain_received_header, 1) > 0 Then
SAHI_trace SAHI_trace_information, "SAHI_isOutgoing: Outgoing mail detected"
SAHI_isOutgoing = 1
Exit Function
End If
SAHI_trace SAHI_trace_information, "SAHI_isOutgoing: Incoming mail detected"
SAHI_isOutgoing = 0
End Function
Function SAHI_isAboveDiscardScore(oMessage)
'Synopsis:
' Checks if this Mails spam score is above the SAHI_auto_discard_score
'Possible return values:
' 0 - Mail is below SAHI_auto_discard_score
' 1 - Mail is above SAHI_auto_discard_score
SAHI_isAboveDiscardScore = 0
If SAHI_auto_discard_score = 0 Then
Exit Function
End If
Dim discard_score
discard_score = String(SAHI_auto_discard_score, "*")
Dim mail_score
mail_score = oMessage.HeaderValue("X-Spam-Level")
SAHI_trace SAHI_trace_debug, "SAHI_isAboveDiscardScore: discard_score: " & discard_score & " X-Spam-Level: " & mail_score & " X-Spam-Status: " & oMessage.HeaderValue("X-Spam-Status")
If InStr(1, mail_score, discard_score) > 0 Then
SAHI_trace SAHI_trace_debug, "SAHI_isAboveDiscardScore: Mail is marked to be discarded"
SAHI_isAboveDiscardScore = 1
End If
End Function
'-----------------------------------------------------------------------------
' scanning
'-----------------------------------------------------------------------------
Function SAHI_scan(mail_file)
'Synopsis:
' Checks the mail_file for spam with a call to Spamassassin
'Possible return values:
' -1 - an error occured
' 0 - ham was found
' 1 - spam was found
SAHI_trace SAHI_trace_debug, "SAHI_scan: Checking mail for spam"
SAHI_scan = 0
Dim checked_file
checked_file = mail_file & ".tmp"
Dim cmd
cmd = SAHI_default_scan_command_line & " < """ & mail_file & """ > """ & checked_file & """"
Dim rc
rc = SAHI_executeCommand(cmd)
If rc = 0 Then
SAHI_trace SAHI_trace_information, "SAHI_scan: Ham found"
ElseIf rc = 1 Then
SAHI_trace SAHI_trace_information, "SAHI_scan: Spam found"
SAHI_scan = 1
ElseIf Len(SAHI_alternate_scan_command_line) > 0 Then
SAHI_trace SAHI_trace_warning, "SAHI_scan: Default command failed with error " & rc & "; trying to recover with alternate command"
cmd = SAHI_alternate_scan_command_line & " < """ & mail_file & """ > """ & checked_file & """"
rc = SAHI_executeCommand(cmd)
If rc = 0 Then
SAHI_trace SAHI_trace_information, "SAHI_scan: Ham found"
ElseIf rc = 1 Then
SAHI_trace SAHI_trace_information, "SAHI_scan: Spam found"
SAHI_scan = 1
Else
SAHI_trace SAHI_trace_alert, "SAHI_scan: Error found: Alternate command failed with error " & rc
SAHI_scan = -1
End If
Else
SAHI_trace SAHI_trace_alert, "SAHI_scan: Error found: Default command failed with error " & rc
SAHI_scan = -1
End If
'Even if Spamassassin has flagged the mail as spam, we better ignore it in
'case the resulting file was empty or not present at all
If SAHI_fso.FileExists(checked_file) = 0 Then
SAHI_trace SAHI_trace_alert, "SAHI_scan: Error found: Resulting file not created, see previous messages for probably reported error"
SAHI_scan = -1
ElseIf SAHI_fso.GetFile(checked_file).Size = 0 Then
SAHI_fso.DeleteFile checked_file
SAHI_trace SAHI_trace_alert, "SAHI_scan: Error found: Resulting file is empty, see previous messages for probably reported error"
SAHI_scan = -1
Else
SAHI_trace SAHI_trace_debug, "SAHI_scan: Replacing mail file with checked result file"
SAHI_fso.DeleteFile mail_file
SAHI_fso.MoveFile checked_file, mail_file
End If
End Function
'-----------------------------------------------------------------------------
' learning
'-----------------------------------------------------------------------------
Function SAHI_learn(oMessage, mail_file)
'Synopsis:
' Check for feeding of Bayesian learning mailboxes with ham, spam or forget
' We do not care about return values of the tool because the ham, spam
' and forget mailboxes are only virtual and therefore we don't want to
' deliver the mail to any physical mailbox regardless if the tool
' was successful or not
'Possible return values:
' 0 - Mail recipient was not a mailbox configured for Bayesian learning tool
' 1 - Mail was feed to Bayesian learning tool
SAHI_trace SAHI_trace_debug, "SAHI_learn: Checking recipients for Bayesian learning mailbox"
SAHI_learn = 0
Dim rc
Dim i
For i = 0 To oMessage.Recipients.Count - 1
Dim recipient_address
recipient_address = oMessage.Recipients.Item(i).Address
If recipient_address = SAHI_learn_ham_mailbox Then
SAHI_learnFood oMessage, mail_file, "ham"
SAHI_learn = 1
Exit Function
ElseIf recipient_address = SAHI_learn_spam_mailbox Then
SAHI_learnFood oMessage, mail_file, "spam"
SAHI_learn = 1
Exit Function
ElseIf recipient_address = SAHI_learn_forget_mailbox Then
SAHI_learnFood oMessage, mail_file, "forget"
SAHI_learn = 1
Exit Function
End If
Next
SAHI_trace SAHI_trace_debug, "SAHI_learn: Recipient not identified as Bayesian learning mailbox"
End Function
Sub SAHI_learnFood(oMessage, mail_file, salearn_food)
Dim attachment_file
'sa-learn needs extra quoting for curly braces in file names - so avoid them
attachment_file = SAHI_stripIllegalChars(mail_file & ".dat", Array("{", "}"))
SAHI_trace SAHI_trace_debug, "SAHI_learnFood: Processing attachments to Bayesian learning tool using " & attachment_file
Dim i
For i = 0 To oMessage.Attachments.Count - 1
oMessage.Attachments.Item(i).SaveAs(attachment_file)
SAHI_feedMessage attachment_file, salearn_food
SAHI_fso.DeleteFile attachment_file
Next
SAHI_trace SAHI_trace_debug, "SAHI_learnFood: " & i & " attachment(s) processed by Bayesian learning tool"
End Sub
Sub SAHI_feedMessage(mail_file, salearn_food)
Dim cmd
cmd = SAHI_learn_command_line & salearn_food & " """ & mail_file & """"
Dim rc
rc = SAHI_executeCommand(cmd)
If rc = 0 Then
SAHI_trace SAHI_trace_information, "SAHI_feedMessage: Learned about " & salearn_food
Else
SAHI_trace SAHI_trace_critical, "SAHI_feedMessage: Learning about " & salearn_food & " failed with error " & rc
End If
End Sub
'-----------------------------------------------------------------------------
' tools
'-----------------------------------------------------------------------------
Function SAHI_executeCommand(command)
Dim rc
SAHI_trace SAHI_trace_debug, "SAHI_executeCommand: command='" & command
rc = SAHI_sh.Run(command, 0, True)
SAHI_trace SAHI_trace_debug, "SAHI_executeCommand: commad returned with " & rc
SAHI_executeCommand = rc
End Function
Sub SAHI_setHeaderValue(oMessage, header_item, header_value)
oMessage.HeaderValue(header_item) = header_value
oMessage.Save()
End Sub
Function SAHI_stripIllegalChars(str, illegal_chars)
' illegal_chars = Array("", "/", ":", ";", "*", """", "<", ">", "|", "=", "?", "[", "]", Chr(34), Chr(39))
Dim char
For Each char In illegal_chars
str = Replace(str, char, "_")
Next
SAHI_stripIllegalChars = str
End Function
Sub SAHI_trace(level, text)
If level <= SAHI_trace_level Then
EventLog.Write(SAHI_trace_id & " | " & SAHI_trace_str(level) & "(" & level & ") | " & text)
End If
End Sub