I decided to start sharing some of my submissions for the 2011 Microsoft Scripting Games that received decent ratings. This is my entry for Advanced Event 3, where the idea was to create a script for event log gathering. The unique part is how a single scriptblock is invoked two different ways. I don’t know that I would always integrate this into a single function, but it’s an interesting example of how flexible scriptblocks can be.
Any code that needs to reach out and touch other computers is going to benefit from PowerShell remoting. The more computers you have to connect to, the more efficient it is to use remoting. I wanted to utilize remoting the way I would in my own production environment, but I also wanted the option to use the built-in remoting capabilities found in the Get-WinEvent cmdlet. Not everyone is lucky enough to have remoting configured everywhere.
I created a scriptblock that was essentially a self-contained script with parameters. All of the actual “work” is handled in this scriptblock. The rest of the function is figuring out how to launch that scriptblock.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
$ScriptBlock = { Param ( [String]$Name, [Int]$NumOfEvents, [Int]$NumOfDays, [Int]$ID, [Int]$Level ) $filter = @{LogName=""} if ($ID) { $filter.Add("ID",$ID) } if ($Level) { $filter.Add("Level",$Level) } if (Test-Connection -ComputerName $Name -Count 1 -Quiet) { $logNames = Get-WinEvent -ComputerName $Name -ListLog * | Where-Object {$_.RecordCount -and $_.IsEnabled} foreach ($log in $lognames) { $filter.LogName = $log.LogName Get-WinEvent -ComputerName $Name ` -FilterHashTable $filter ` -MaxEvents $NumOfEvents ` -ErrorAction SilentlyContinue | Where-Object {$_.TimeCreated -ge (Get-Date).AddDays(-$NumOfDays).Date} } } } |
With the script-within-a-script ready, I just needed to launch it appropriately. The Get-LoggedEvent function has a UseRemoting switch. When this switch is specified, I want to use Invoke-Command to take advantage of PowerShell’s fan-out remoting capabilities. I pass Invoke-Command the array of names, the scriptblock and the arguments to be passed to the scriptblock. The scriptblock is given ‘localhost’ for the computer name, and Invoke-Command takes care of running a copy of the scriptblock on each computer.
1 2 3 4 5 6 7 8 9 10 |
if ($UseRemoting) { Invoke-Command -ComputerName $ComputerName ` -ScriptBlock $ScriptBlock ` -ArgumentList 'localhost', ` $NumberOfEvents, ` $NumberOfDays, ` $EventID, ` $LevelTable[$Severity] } |
If UseRemoting is not specified, I need to unwrap the array of computer names and call the scriptblock once for each computer name. This time, I need to pass the actual computer name to the scriptblock, because the built-in remoting capabilities of Get-WinEvent will be used instead of letting Invoke-Command run the scriptblock locally on each system.
1 2 3 4 5 6 7 8 9 10 11 |
else { foreach ($Name in $ComputerName) { &$ScriptBlock -Name $Name ` -NumOfEvents $NumberOfEvents ` -NumOfDays $NumberOfDays ` -ID $EventID ` -Level $LevelTable[$Severity] } } |
With any script, there are always improvements that can be made. Bartek Bielawski (who ended up winning the 2011 Scripting Games) made a good suggestion that I use splatting for those long lists of parameters instead of backticks. He also pointed out that I don’t need to specify false as a default value for switch parameters because the value will already default to false.
1 |
$UseRemoting = $false |
Has the same effect as:
1 |
$UseRemoting |
All of my entries for the 2011 Scripting Games can be found at PoshCode.
Complete Script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# ----------------------------------------------------------------------------- # Script: Get-LoggedEvent.ps1 # Author: Jason Hofferle # Date: 04/06/2011 # Version: 1.0.0 # Comments: This script is based around the Get-LoggedEvent function. This # function was designed with the intent that gathering a list of computer # names and formatting output would be done by other cmdlets in the pipeline. # These type of tasks gain so much speed through PowerShell remoting that an # optional parameter was added that will use fan-out remoting instead of # querying each computer individually. # ----------------------------------------------------------------------------- Function Get-LoggedEvent { [CmdletBinding()] Param ( [String[]] $ComputerName = $Env:ComputerName, [Int] $NumberOfEvents = 1, [Int] $NumberOfDays = 0, [Int] $EventID, [ValidateSet("Critical","Error","Warning","Informational")] [String] $Severity, [Switch] $UseRemoting = $false ) Begin { # For converting human-friendly text to numbers used in query $LevelTable = @{ Critical=1 Error=2 Warning=3 Informational=4} # Specify a scriptblock that can be run locally, or passed to # Invoke-Command when using PowerShell remoting. $ScriptBlock = { Param ( [String]$Name, [Int]$NumOfEvents, [Int]$NumOfDays, [Int]$ID, [Int]$Level ) $filter = @{LogName=""} if ($ID) { $filter.Add("ID",$ID) } if ($Level) { $filter.Add("Level",$Level) } if (Test-Connection -ComputerName $Name -Count 1 -Quiet) { $logNames = Get-WinEvent -ComputerName $Name -ListLog * | Where-Object {$_.RecordCount -and $_.IsEnabled} foreach ($log in $lognames) { $filter.LogName = $log.LogName Get-WinEvent -ComputerName $Name ` -FilterHashTable $filter ` -MaxEvents $NumOfEvents ` -ErrorAction SilentlyContinue | Where-Object {$_.TimeCreated -ge (Get-Date).AddDays(-$NumOfDays).Date} } } } } Process { if ($UseRemoting) { # Pass the entire array of computer names to Invoke-Command Invoke-Command -ComputerName $ComputerName ` -ScriptBlock $ScriptBlock ` -ArgumentList 'localhost', ` $NumberOfEvents, ` $NumberOfDays, ` $EventID, ` $LevelTable[$Severity] } else { # Unwrap $ComputerName array and invoke the scriptblock for each computer foreach ($Name in $ComputerName) { &$ScriptBlock -Name $Name ` -NumOfEvents $NumberOfEvents ` -NumOfDays $NumberOfDays ` -ID $EventID ` -Level $LevelTable[$Severity] } } } <# .Synopsis Returns recent events from local and remote computers. .Description The Get-LoggedEvent function queries event logs and event trace logs and returns the most recent events from each log. .parameter ComputerName Gets the event information on the specified computers. The default is the local computer name. .parameter NumberOfEvents Specifies the number of events to return from each log. The default is to return only the latest event. .parameter NumberOfDays Specifies the number of past days to query for events. The default is 0, which only returns events logged today. .parameter EventID Gets only the events with the specified event ID. The default is all events. .parameter Severity Gets only the events with the specified Level. Valid values are Critical, Error, Warning and Informational. The default is all types. .parameter UseRemoting Instead of querying each computer individually, use PowerShell remoting to connect to the remote systems specified in the ComputerName property. The default is to not use remoting. .Example Get-LoggedEvent Description ----------- This command gets the latest event from each event log on the local computer, if there have been events logged today. .Example Get-LoggedEvent -NumberOfEvents 10 -NumberOfDays 3 -Severity Warning Description ----------- This command gets the latest 10 Warning events from each event log that have been logged in the last three days. .Example Get-LoggedEvent -ComputerName DC1,DC2,WIN7 -UseRemoting Description ----------- This command gets event information from the three computers specified using PowerShell remoting. #> } |