Script logging
As you create your enterprise script template, you will need to incorporate a logging mechanism. Logging allows you to capture script output, including informational, warning, and error messages. In typical logging scenarios, you will need to record script actions to either the event log, a log
file, or a data collection file, like a Comma Separated Values (CSV) file. While PowerShell has a transcript which you can invoke, leveraging the start-transcript and stop-transcript cmdlets, it only allows you to record output to a single log
file. This doesn't provide for writing to the event log or data collection files.
A popular logging mechanism is to create your own PowerShell logging function. This enables you to pass in parameters into the logging
function to tell the script to either write to the event log, log
file, or append data to the data collection file. It can also write the actions to the PowerShell window to view progress. This avoids having to write multiple lines of code each time you want something to be displayed in either the event log, log
file, or the data collection file. You just need to call the logging
function and pass in the required parameters, and it will write to the locations you specify.
Creating the logging files
To start, you will need to create the files for logging. Enterprises typically include both the server name and a date timestamp for log files. This allows for a clear identity of log files and execution times in large environments.
To create a time-stamped log
file, you can perform the following:
$date = (Get-Date -format "yyyyMMddmmss") $compname = $env:COMPUTERNAME $logname = $compname + "_" + $date + "_ServerScanScript.log" $scanlog = "c:\temp\logs\" + $logname new-item -path $scanlog -ItemType File -Force
The output of creating the log
file is shown in the following screenshot:
The preceding script displays how to create a .log
file for script logging. To start, you leverage the Get-Date
cmdlet with the -format
parameter set to "yyyyMMddmmss"
and place it into the $date
variable. The $date
variable will then have sequentially the four-digit year, two-digit month, two-digit day, two-digit minute, and two-digit seconds, from when the script was executed, contained in it. You then gather the computer name by leveraging the $env:COMPUTERNAME
environment variable and placing the value into the $compname
variable. You then combine the $compname
variable with a plus symbol, an underscore for separation with a plus symbol, the date timestamp with a plus symbol, and log
file name of "ServerScanScript.log",
and store it in the $logname
variable. You then create the full log path by specifying "c:\temp\logs\"
with a plus symbol and the $logname
directory and store it in the $scanlog
variable. Finally, you leverage the new-item
cmdlet with the -path
parameter set to $scanlog
, -ItemType
set to File
, and the -Force
parameter. After execution, you will have a log
file in c:\temp\logs\
with the filename of computername_datetimestamp_ServerScanScript.log
.
To create a time-stamped data collection file in the format of a CSV file, you can perform the following:
$date = (Get-Date -format "yyyyMMddmmss") $compname = $env:COMPUTERNAME $logname = $compname + "_" + $date + "_ScanResults.csv" $scanresults = "c:\temp\logs\" + $logname new-item -path $scanresults -ItemType File -Force # Add Content Headers to the CSV File $csvheader = "ServerName, Classification, Other Data" Add-Content -path $scanresults -Value $csvheader
The output of creating the CSV file is shown in the following screenshot:
The preceding script displays how to create a data collection file in the format of a .csv
file for script logging. To start, you leverage the Get-Date
cmdlet with the -format
parameter set to "yyyyMMddmmss"
and place it into the $date
variable. The $date
variable will then have sequentially the four-digit year, two-digit month, two-digit day, two-digit minute, and two-digit seconds, from when the script was executed, contained in it. You then gather the computer name by leveraging the $env:COMPUTERNAME
environment variable and placing the value into the $compname
variable. You then combine the $compname
variable with a plus symbol, an underscore for separation with a plus symbol, the date timestamp with a plus symbol, and log
file name of _ScanResults.csv
, and store it in the $logname
variable. You then create the full log path by specifying "c:\temp\logs\"
with a plus symbol and the $logname
directory, and store it in the $scanresults
variable. You will then leverage the new-item
cmdlet with the -path
parameter set to $scanlog
, the -ItemType
set to File
, and the -Force
parameter. After execution, you will have a .csv
file in c:\temp\logs\
with the filename of computername_datetimestamp_ScanResults.csv
.
Finally, to add the headers to the CSV file, you specify the header names separated by values as "ServerName, Classification, Other Data"
and store it in the $csvheader
variable. You then add the header to the CSV file by leveraging the Add-content
cmdlet with the -path
parameter set to $scanresults
and the -value
parameter set to $csvheader
. The computername_datetimestamp_ScanResults.csv
file will have the headers of ServerName
, Classification
, and Other Data
.
When opening the CSV file in excel, it displays the headers as follows:
Tip
You may choose to optimize this code by not adding the computer name using the $compname
variable. Alternatively, you can call the $env:COMPUTERNAME
variable directly when setting the $logname
variable. The script was written to display the progression of the log
file string with the computer name, date timestamp, and script filename.
Creating a windows event log source
In the instance that you want to write to the Windows event log, one of the prerequisites is to register the script as an event source with the Windows event log system. This is done with the use of the New-EventLog
cmdlet. You start by specifying the New-EventLog
cmdlet, followed by the -LogName
parameter followed by an event log name. You can either select your own event log or specify one of the built-in event logs, such as Application
or system. Finally, you specify the -Source
parameter with the name of the script you are executing.
You can only register the event log source once, which may lead to errors when executing a script multiple times on the same system. You can retrieve the event log sources by specifying the get-eventlog
cmdlet with the -LogName
parameter, and piping the results to Select-object Source -Unique. This is not very desirable as it requires the system to query all the log events to determine if a source is already registered. Since Windows systems have a large number of event logs and log events, the query is very CPU and memory intensive.
The fastest way to get around a duplicate event log source error message is to suppress the error message itself. This is done through leveraging the -ErrorAction
parameter set to SilentlyContinue
with the New-Eventlog
cmdlet. The -ErrorAction
parameter with SilentlyContinue
specified will capture the error and silently continue with the script, suppressing the error messages.
To register a new event log source suppressing error messages, you can perform the following:
New-EventLog –LogName Application –Source "WindowsServerScanningScript" -ErrorAction SilentlyContinue
The output of the PowerShell window will look like the following:
The preceding script displays how to register a new event log source with the Windows event log system. You first start by using the New-EventLog
cmdlet with the -LogName
parameter set to Application
. You then specify the -Source
argument and set it to the script name of "WindowsServerScanningScript"
. Finally, you suppress error messages by using the -ErrorAction
argument and setting it to SilentlyContinue
. After executing this command, you will be able to write to the Application
event log with the event source of WindowsServerScanningScript
.
Note
The Windows event log cmdlets require that the PowerShell Window is Run as administrator. When testing this code, you will want to right-click the PowerShell icon and Run as administrator. If you are remotely executing this code, you may want to ensure the user credentials you are using have administrative rights to the system.
Creating the logging function
After creating the log files and registering the event log source, you are now able to create a custom logging
function. The logging
function will be flexible in that it can accept arguments for three types of data writes. These include event logs, log files, and the data collection .csv
file.
The logging
function is declared with functionlog {
. You will then need to leverage a parameter block to accept arguments into the function. The parameter block is declared with param() and includes the variables $string
, $scnlg
, and $evntlg
. $string
will be the string value for the informational, warning, error, or data collection content. The $scnlg
variable dictates if the content is written to the scan log
file. The $evntlg
variable dictates if the content is written to the event log.
To create the beginning of the logging
function, type the following:
function log { param($string, $scnlg, $evntlg)
Using a variation of the arguments with the logging
function, you can write log information to different locations. You can set the following:
- If
$scnlg
is set to Y and no value is specified for$evntlg
, the script will only write to the scanninglog
file. - If
$scnlg
is set to N and$evntlg
is set to Y, the script will write only to the event log. - If
$scnlg
is set to Y and$evntlg
is set to Y, the script will write to both the scanninglog
file and the event log. - If no parameters other than
$string
are passed into the function, it will write the contents to the data collection CSV file. - In all cases, the
logging
function will leverage thewrite-host
cmdlet with the contents of the$string
variable. This will verbose print all the logging variations.
To create the full logging
function, you can perform the following:
function log { param($string, $scnlg, $evntlg) # If Y is populated in the second position, add to log file. if ($scnlg -like "Y") { Add-content -path $scanlog -Value $string } # If Y is populated in the third position, Log Item to Event Log As well if ($evntlg -like "Y") { write-eventlog -logname Application -source "WindowsServerScanningScript" -eventID 1000 -entrytype Information -message "$string" } # If there are no parameters specified, write to the data collection file (CSV) if (!$scnlg) { $content = "$env:COMPUTERNAME,$string" Add-Content -path $scanresults -Value $content } # Verbose Logging write-host $string } $date = Get-Date log "Starting WindowsServerScanningScript at $date ..." "Y" "Y" log "Writing a message to the Event Log Only." "N" "Y" log "ScriptStart,$date"
The output of this script will look like the following screenshot:
This example displays how to create a logging
function and log information to a log
file, event log, and data collection CSV file. To start, you first declare function log {
. You then accept three parameters into the function with the param($string, $scnlg, $evntlg)
section of code. You then create an IF statement to determine if the $scnlg
contents are -like "Y"
. If it returns true, you then execute the Add-content
cmdlet with the -path
parameter set to $scanlog
, and the -Value
parameter set to $string
. The contents of the string will now be in the $scanlog
file.
The second IF statement evaluates if the $evntlg
contents are -like "Y"
. If it returns True
, you then leverage the write-eventlog
cmdlet with the -logname
parameter set to Application
. You also include the -source
parameter set to "WindowsServerScanningScript"
, the -eventID
parameter set to the event ID number of 1000, the -entrytype
parameter set to Information
, and the -message
attribute set to $string
. The contents of the string will now be in the Application
event log under the source of WindowsServerScanningScript
and the event ID of 1000.
Tip
Some Enterprise environments have monitoring solutions that can parse the event log and trigger actions based on the event IDs. As an alternative to passing in a "Y"
, you can pass in an event ID which can automatically trigger alerts with those monitoring systems. You would have to update the if
statement to read if
($evntlg) {}
, which will return True
if data is contained in the variable. You can then set the -eventID
parameter to $evntlg
in the script, and you have the ability to change the event ID as needed.
The third if
statement determines if there is any data in the $scnlg
variable. This means the default action will be to write to the data collection CSV file. You leverage the if
statement with an exclamation point preceding the $scnlg
variable. This specifies if
NOT
True
, or if there is not any data being passed in that variable, to proceed to the next steps. You then specify the environment variable of $env:COMPUTERNAME
, followed by a comma, followed by the $string
variable, and set the value in the $content
variable. You then write the $content
variable to the CSV file by leveraging the Add-content
cmdlet with the -path
parameter set to $scanresults
, and the -value
parameter set to $content
.
The final step in the function is to display the content by using the write-host
cmdlet with $string
as the content.
To test the script, you then obtain the date timestamp by using the Get-Date
cmdlet and setting the value in the $date
variable. You then call the log
function with the first argument of "Starting WindowsServerScanningScript at $date"
, the second argument of "Y"
, and the third argument of "Y"
. This writes the content in the first argument to both the log
file and the event log.
The following displays the contents of the created log
file:
The following displays the contents of the event log generated by the script:
You then call the log
function again with the first argument of "Writing A Message to the Event Log Only."
, with the second argument of "N"
, and the third argument set to "Y"
. This writes to the Application
log with the source of WindowsServerScanningScript
and the Event ID of 1000.
The following displays the contents of the event log generated by the script:
Finally, you call the log
function with the first argument set to "Script,$date"
. No other arguments are specified with this function. The script will then write to the data collection CSV file with the following syntax—"computer name, ScriptStart, DateTimestamp"
.
The following displays the contents of the created data collection CSV file: