Using Windows EventLog for Script Logging

Using the built-in Windows event log architecture for script logging is great, and very simple. Here are the initial steps to prepare logging (requires Administrator privileges):

#requires -runasadministrator

New-EventLog -LogName PSScriptLog -Source Logon, Installation, Misc, Secret
Limit-EventLog -LogName PSScriptLog -MaximumSize 10MB -OverflowAction OverwriteAsNeeded

You may want to adjust the log name and/or the names of the error sources. These sources can be anything provided it has not yet been assigned to any event log.

Now, every regular user can use the below code to write to the new event log:

Write-EventLog -LogName PSScriptLog -Source Logon -EntryType Warning -EventId 123 -Message "Problem in script $PSCommandPath"

Note how Write-EventLog uses the same logfile and one of the source names that you defined in your first code.

Once your scripts log information to your log file, you can easily search for events or build reports by using Get-EventLog:

 
PS C:> Get-EventLog -LogName PSScriptLog -EntryType Error -Message *test.ps1* 
 

Twitter This Tip! ReTweet this Tip!

Working with [FileInfo] Object

Often, code needs to check on files, and for example test whether the file exists or exceeds a given size. Here is some commonly used code:

$logFile = "$PSScriptRootmylog.txt"

$exists = Test-Path -Path $logFile
if ($exists)
{
  $data = Get-Item -Path $logFile
  if ($data.Length -gt 100KB)
  {
    Remove-Item -Path $logFile
  }

}

By immediately converting a string path into a FileInfo object, you can do more with less:

[System.IO.FileInfo]$logFile = "$PSScriptRootmylog.txt"
if ($logFile.Exists -and $logFile.Length -gt 0KB) { Remove-Item -Path $logFile }

You can convert any path to a FileInfo object, even if it is not representing a file. That’s what the property “Exists” is for: it tells you whether the file is present or not.

Twitter This Tip! ReTweet this Tip!

Script Logging Made Easy

Beginning in PowerShell 5, you can use Start-Transcript in any host to log all the output created by your script. Here is how you can easily add logging capabilities to any script:

# add this: ############################
$logFile = "$PSScriptRootmylog.txt"
Start-Transcript -Path $logFile -Append
#########################################

"Hello"

($a = Get-Service)

"I received $($a.Count) services."
Write-Host "Watch out: direct output will not be logged!"


# end logging ###########################
Stop-Transcript
#########################################

Simply add the code in the comment blocks to the start and end of your script. The log file will be created in the same folder the script resides in. Since $logFile uses $PSScriptRoot (the current folder of your script), always make sure you save the script and run it as a script. Else, $PSScriptRoot would be empty.

Just make sure your script outputs all the information you want to see in your logfile. By placing assignments into parenthesis, for example, PowerShell will not just assign the values but also echo them to the output.

One caveat: any input and output will be logged, except for messages written directly to the host using Write-Host. Such messages are always visible only to the user on screen.

Twitter This Tip! ReTweet this Tip!

Multi-Language Voice Output

On Windows 10, the operating system ships with a bunch of high-quality text-to-speech engines and is no longer limited to just the English language. The number of available TTS Engines depends on the languages you installed.

PowerShell can send text to these TTS Engines, and via tags can also control the language used. If you have both the English and German TTS Engines installed, you could mix languages like below:

$text = "">Your system will restart now!</LANG>
<LANG LANGID=""407""><PITCH MIDDLE = '2'>Oh nein, das geht nicht!</PITCH></LANG>
<LANG LANGID=""409"">I don't care baby</LANG>
<LANG LANGID=""407"">Ich rufe meinen Prinz! Herbert! Tu was!</LANG>
"

$speaker = New-Object -ComObject Sapi.SpVoice
$speaker.Rate = 0
$speaker.Speak($text)

If you want to use different languages, simply adjust the LANGID number to the culture you would like to use.

Twitter This Tip! ReTweet this Tip!

Removing Text from Strings

Occasionally, you might read about Trim(), TrimStart(), and TrimEnd() to remove text from strings. And this seems to really work well:

 
PS C:> $testvalue = "this is strange"
PS C:> $testvalue.TrimEnd("strange")
this is 

PS C:> 
 

But what about this?

 
PS C:> $testvalue = "this is strange"
PS C:> $testvalue.TrimEnd(" strange")
this i

PS C:>  
 

The truth is that Trim() methods treat your argument as a list of characters. Any of these characters are removed.

If you just want to remove text from a string anywhere, use Replace() instead:

 
PS C:> $testvalue.Replace(" strange", "")
this is

PS C:>  
 

If you want more control, use regular expressions and anchors. To remove text from the end of a string only, the below code would do the trick. Only the word “strange” that ends the string would be removed.

$testvalue = "this is strange strange strange"

$searchText = [Regex]::Escape("strange")
$anchorTextEnd = "$"
$pattern = "$searchText$anchorTextEnd"

$testvalue -replace $pattern

Twitter This Tip! ReTweet this Tip!

Uncompressing Serialized Data

In the previous tip you learned how you can use Export-CliXml to serialize data and then use Compress-Archive to shrink the huge XML files to only a fraction of original size.

Today, we do the opposite: we take a ZIP file that contains XML serialization data, and restore (“rehydrate”) the serialized objects. This of course assumes you created such a file with yesterday’s tip.

# path to existing ZIP file
$ZipPath = "$env:TEMPdata1.zip"

# by convention, XML file inside the ZIP file has the same name
$Path = [IO.Path]::ChangeExtension($ZipPath, ".xml")

# expand ZIP file
Expand-Archive -Path $ZipPath -DestinationPath $env:temp -Force

# deserialize objects
$objects = Import-Clixml -Path $Path 

# remove XML file again
Remove-Item -Path $Path -Recurse -Force

$objects | Out-GridView

Twitter This Tip! ReTweet this Tip!