The other day on Twitter I saw a message about new script in the Microsoft Script Center on getting remote event logs with WMI. So I took a look at the script. If you take a minute to look at the script you'll quickly realize this is not a script for beginners. My initial thought was "Why?". We already have cmdlets in PowerShell for querying event logs on remote computers. I realize the script was trying to avoid some of the issues we run into with WMI and I can't question the effectiveness of his function. It works and with out a doubt using runspaces like this is faster than cmdlets.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
My concern when I see scripts like this is that someone new to PowerShell will see it and run to the hills thinking they'll never be able to use PowerShell and that is definitely not the case. So I decided to see what I could come up with that used a more IT Pro friendly cmdlet-based approach. I wanted to write something that most of you could have come up with.
My first attempt is a function that uses Invoke-Command to run the Get-Eventlog cmdlets in a remote session. In the function I define a scriptblock that gets all the event logs with records, and then gets all non-information or SuccessAudit events from those logs that have happended since midnight yesterday. My function supports credentials and takes advantage of a few other features from Invoke-Command.
Function Get-RecentEventLog { [CmdletBinding()] [OutputType([System.Diagnostics.EventLogEntry])] Param( [Parameter(Position=0,HelpMessage="Enter the name of a computer")] [ValidateNotNullorEmpty()] [Alias("cn","pscomputername","name")] [string[]]$Computername=$env:COMPUTERNAME, [ValidateScript({$_ -ge 1})] [int]$Days=1, [Alias("RunAs")] [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, [ValidateScript({$_ -ge 1})] [int]$ThrottleLimit=32, [switch]$Asjob, [string]$JobName ) Begin { Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" } #begin Process { #define a scriptblock to search event logs that will run remotely via Invoke-Command $sb = { $logs = Get-Eventlog -List | where {$_.entries.count -gt 0} if ($logs) { #construct a filter hashtable $filterParams= @{ Logname= $Null After= (Get-Date).AddDays(-$using:Days).Date EntryType= "error","warning","failureaudit" ErrorAction="stop" } foreach ($log in $logs) { $filterParams.Logname = $log.log #add the log name to each entry Write-Verbose "Processing $($log.log) on $env:computername" Try { Get-Eventlog @filterParams | Add-Member -MemberType NoteProperty -Name Logname -value $log -PassThru } Catch { #ignore errors because they are probably for no matching records } } #foreach log } #if $logs } #close scriptblock #create a hashtable of parameters for Invoke-Command #use the bound parameters as a starting point $icmParam=$PSBoundParameters if ($icmParam.ContainsKey("Days")) { #remove Days since it isn't used by Invoke-Command $icmParam.Remove("Days") } #add the other parameters $icmParam.Add("Scriptblock",$sb) $icmParam.Add("ErrorAction","Stop") $icmParam.Add("ErrorVariable","myErr") Try { Write-Verbose "Initiating Invoke-Command" Write-Verbose "Getting event logs from the past $days day(s)" Invoke-Command @icmParam } Catch { Write-Warning "Failed to run Invoke-Command" Write-Warning $myErr.ErrorRecord } } #process End { Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" } #end } #close function Get-RecentEventLog
The function works and took a about 1 1/2 min to query 8 machines in my virtual test environment. There is definitely some overhead when using Invoke-Command, but the trade off is a script that is a little easier to develop and maintain.
Then I thought, what about a workflow? I'm querying event logs but there's no reason I can't query all of them simultaneously. Here's my workflow that does essentially the same thing as my function.
WorkFlow Get-RecentEventLog { Param( [int]$Days=1 ) Write-Verbose -Message "$(Get-Date) Starting $workflowcommandname" #get event logs that have values $logs = Get-Eventlog -List | Where-Object -filter {$_.entries.count -gt 0} #search each log in parallel foreach -parallel ($log in $logs) { Write-Verbose -message "Processing $($log.log)" Sequence { $After= InlineScript { (Get-Date).AddDays(-$using:Days).Date} $EntryType= "error","warning","failureaudit" Try { Get-Eventlog -LogName $log.log -After $After -EntryType $EntryType -ErrorAction Stop | Add-Member -MemberType NoteProperty -Name Logname -value $log -PassThru } Catch { Write-Verbose -Message "No matching entries found in $($log.log)" } } #sequence } #foreach parallel Write-Verbose -Message "$(Get-Date) Ending $workflowcommandname" }
Interestingly, in my tests the workflow took about the same amount of time to run. But this is a shorter script to develop because all of the features like remoting, credentials and jobs are automatically part of the workflow. There is a potential downside in that all the remote machines must be running PowerShell 3.0. This workflow is also a great example in that workflows aren't always the answer. There's nothing really here, other than potentially the use of parallelism, that makes this a better choice than my function.
My last concern with the gallery script, and I don't know if this would have an effect on its performance, is that all the event logs are rolled up in a property for each computer. This means you have to take some further steps to expand and format the results. My function and workflow, because they rely on Get-Eventlog, are formatted and ready to go.
What I haven't tried yet, is how this same task can be done with Get-WinEvent or Get-CimInstance. The latter helps avoid some of the issues with WMI and might perform better. If I have time, I'll get back to you with my results. But in the meantime, what do you think about all of this?
Using runspaces in PowerShell has a place, but definitely requires an advanced skill set and a willingness to accept a tradeoff of a more complicated script to develop and maintain (especially if someone else has to) with improved performance. I can see where using a runspace approach makes sense when you have 1000s of computers. But I might also argue that if that is your environment, you probably have full-blown management suites. Yes, I know there will always be exceptions. But for the majority of you, are you happy writing scripts that use existing cmdlets or do you feel obligated to learn .NET before you can use PowerShell?
UPDATE:
I tried this using Get-CIMInstance. Yes, this requires PowerShell 3 remotely (unless you take an extra step to setup a DCOM CIM session) and the use of the WSMan protocol, but this performs suprisingly well and only takes a few lines of PowerShell.
#convert date time to WMI format $after = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime((Get-Date).AddDays(-1).date) #get all non information and success entries $filter = "TimeGenerated >= ""$after"" AND (Type <> 'Information' AND type <> 'Audit Success')" $computers = "chi-dc01","chi-dc04","chi-hvr2","chi-core01","chi-win81","chi-fp02" Get-CimInstance win32_ntlogevent -filter $filter -ComputerName $computers
I ran this in my test environment and it took about 30 seconds to return 188 event log entries. Because we're using WSMAN we avoid some of the issues with RPC and DCOM. So here is a solution, at least in this case, that is as fast as using runspaces but only took a few minutes to write and is easy to follow.
Everything we do as IT Pros is a matter of balancing trade-offs and working within a set of limitations.
This is a great review of the tradeoffs. In the end, pick what works for what you need to do and is within your budget of time, skill, etc.
My only issue is with your comment that if you have 1000’s of computers you probably have a full-blown management suite so this is moot. I do have 1000’s of computers and yes we have a management suite, but those suites are big and complicated. Some things require major development exercises to do in them. If I plan to do it once, and it is not something that suite does out of the box, I would probably write it in PowerShell instead. If the time to run was an issue I’d use workflows or runspaces to get the parallelism I needed.
Thanks for taking the time to share your thoughts. Sure, big management suites aren’t always the answer. But I’m a big proponent of the right tool for the job. If someone has 1000s of computers and takes the time to write a PowerShell script full on .NET code, my thought is why not take the extra step and create a .NET compiled tool. Script will always be slower. Of course I’m assuming that if you have the skill to write such as script you can probably use Visual Studio and build a utility. I suppose for me, if I see a script that is full of .NET code, it is missing the point of PowerShell.
When I write a script I start with the simple way, using native PowerShell. Then I see where that does not work. Sometimes that leads me into .Net. Sure, I can turn the whole thing into C# at that point, but I had a reason to not start with C# and that reason is probably still valid.
Every script is a set of trade offs. If I see a lot of .Net I do wonder why the author chose that route. Sometimes they had a good reason and sometimes they didn’t.
Thanks Jeffery! I like the pure version best.
Hi,
Thx for the rebuttal scripts 🙂
If you’ve a look at Zachary Loeber’s contributions on the technet, you’ll see that he’s using a template based on runspaces
http://gallery.technet.microsoft.com/scriptcenter/site/search?f[0].Type=User&f[0].Value=Zachary%20Loeber&sortBy=Date
Examples:
http://gallery.technet.microsoft.com/scriptcenter/Get-Remote-Shadow-Volume-e5a72619
http://gallery.technet.microsoft.com/scriptcenter/Retrieve-Remote-Scheduled-72985b8f
(in this one he mentioned that he found a portion of the code on my blog)
http://gallery.technet.microsoft.com/scriptcenter/Gather-Remote-Installed-b78c26d3
Yup, I don’t claim original authorship of the function template but sure have used the heck out of it! The function for gathering event log entries was one of several from this project: http://gallery.technet.microsoft.com/Excel-and-HTML-Asset-0ffbf569.
I eventually wanted to add remoting capabilities to all the functions but it only seemed to make sense in very specific scenarios. Otherwise using multiple runspaces doesn’t seem to give much performance gain over simply specifying multiple systems when remoting. Here is one which was completed: http://gallery.technet.microsoft.com/Retrieve-Remote-Scheduled-72985b8f.
i copied your first script to test it out but i never did get a anything showing up in powershell ? I am using
I’m not sure which script you ran, but if it was one defining the function, you will need to dot source the script first. Or copy and paste the function into your PowerShell session.
Oh thanks. I did not even think that.