$Error[0] is null

I maintain a little module of CLI tools for my team at work and have a behavior i can’t account for.
I keep separate .ps1 files for all function definitions, and then “build” them in to a monolithic .psm1 file before publishing the module.
I say this because when I’m writing and testing the function, it’s a single file and I redefine the function in my terminal (VS Code) whenever there is a change and test.
I’ve been doing error handling like this quite a bit:

try {
    #some code
} catch {
    Write-Warning $Error[0]
}

When I do this during development I get what I wanted:
image
However, the same circumstances leading to the error behave differently once it’s in the module.

When this has happened in the past I know i’ve gone back and tried changing $Error[0] to $Error[0].Message or something like that, and in testing it works, but then in the module it might occur again. Most of the time the errors are few and far between so it’s hard to test.
But this recent function, Get-LockEvent, will always generate an error if the target computer doesn’t have any Event ID 4800 or 4801 in the Security log.

My question is: what’s happening with the automatic $Error variable when my code is executing from my module instead of a single function definition?

Here is the full function definition i’m working with:

function Get-LockEvent {
    <#
    .SYNOPSIS
    Retrieve logs from Windows Events pertaining to computer lock and unlock.
    .DESCRIPTION
    Easier to remember wrapper for Get-WinEvent and a filterhashtable for getting event ID 4800 and 4801 from the Windows Security log.
    .PARAMETER ComputerName
    Can specify a remote computer to pull logs from
    .PARAMETER OutputType
    By default EventLogRecord objects are returned from Get-WinEvent.  You can specify PSObject for output type and the event records will be converted to XML, and then turned in
    to Powershell objects from there for easier reading/exporting/filtering.
    .PARAMETER TimeFrame
    By default it will filter for events occuring today. You can specify "All", "Today" or "LastHour" and Get-WinEvent will filter accordingly. 
    #>
    [cmdletbinding()]
    Param (
        [Parameter(Mandatory=$false,Position=0)]
        [String]$ComputerName,
        [Parameter(Mandatory=$false,Position=1)]
        [ValidateSet("Default","PSObject")]
        [String]$OutputType = "Default",
        [Parameter(Mandatory=$false,Position=2)]
        [ValidateSet("All","Today","LastHour")]
        [String]$TimeFrame = "Today"
    )

    $StartTime = switch ($TimeFrame) {
        "Today" { Get-Date $((Get-Date).ToShortDateString()) }
        "All"   { $false }
        "LastHour" { (Get-Date).AddHours(-1) }
    }

    $EventHT = @{
        FilterHashTable = @{
                LogName = "Security"
                Id      = 4800,4801
        }
        ErrorAction     = "Stop"
    }

    if ($StartTime) {
        $EventHT.FilterHashTable.Add("StartTime",$StartTime)
    }

    if ($ComputerName) {
        $EventHT.Add("ComputerName",$ComputerName)
    }

    try {
        switch ($OutputType) {
            "Default" {
                Get-WinEvent @EventHT
            }
            "PSObject" {
                $Events = Get-WinEvent @EventHT
                Foreach ($Event in $Events) {
                    $XmlEvent = [xml]$Event.ToXml()
                    $EventType = switch ($XmlEvent.Event.System.EventID) {
                        "4800" {"Locked"}
                        "4801" {"Unlocked"}
                    }
                    [PSCustomObject]@{
                        PSTypeName       = "LockEvent"
                        DateTime         = Get-Date $XmlEvent.event.System.timecreated.systemtime -Format 'MM/dd/yy HH:mm:ss'
                        EventID          = $XmlEvent.Event.System.EventID
                        EventType        = $EventType
                        TargetUserSID    = $XmlEvent.Event.EventData.Data.'#text'[0]
                        TargetUserName   = $XmlEvent.Event.EventData.Data.'#text'[1]
                        TargetDomainName = $XmlEvent.Event.EventData.Data.'#text'[2]
                        SessionId        = $XmlEvent.Event.EventData.Data.'#text'[4]
                    }
                }
            }
        } 
    } catch {
        Write-Warning $Error[0]
    }
}

I wrote a basic module that only returns $Error.Count. Looks like $Error in a module is different than the current session $Error

I found that in my Modules, to pass errors back to a calling script, I had to reference:

$Global:Error
1 Like

Why wouldn’t you use $_ in the catch block? I do $_.exception.message

yeah it’s a good question that I don’t have an answer for. I think I picked up the $Error[0] behavior from some example code along the way, and since it’s works when i’m authoring the .ps1 file I just didn’t notice. I will test with the current object variable now.

EDIT: switching the code to use $_ like @krzydoug suggested did the trick. It now processes Write-Warning both in VS Code, and in the terminal when using the compiled module.

According to SeeminglyScience the $error variable is explicitly empty in module scope. He just happened to be talking about it in the powershell discord. He also said $error is best used by the interactive user, not in a script.

1 Like

In my larger scripts, I use $Global:Error in my Modules to grab the exact line of code the script failed on. Is there another way to get this without $Error?