Expert Commentary: 2012 Scripting Games Advanced Event 5

Expert Commentary: 2012 Scripting Games Advanced Event 5

  • Comments 4
  • Likes

Summary: Microsoft MVP, Richard Siddaway, provides expert commentary for 2012 Scripting Games Advanced Event 5.

Microsoft Scripting Guy, Ed Wilson, is here. Richard Siddaway is the expert commentator for Advanced Event 5.

Photo of Richard Siddaway

Richard has been working with Microsoft technologies for over 22 years, and he spent time in most IT roles including analyst-programmer, server administration, support, DBA and architect. He has been interested in automation techniques (including automating job creation and submission on mainframes many years ago!). He has used VBScript and WMI since it became available on NT 4. Windows PowerShell caught his interest when he first heard about it  and he has been using it since the early beta versions.

He founded and runs the UK PowerShell User Group, and he is a Windows PowerShell MVP. He has given numerous talks about Windows PowerShell at various events in the UK, Europe, and the USA. He is a frequent speaker for Windows PowerShell user groups worldwide. He has a number of articles published about Windows PowerShell, including expert commentary’s on the Microsoft Scripting Games for which he has been a judge for the last two years. Windows PowerShell in Practice (Manning) was published in June 2010 and he is currently finishing PowerShell and WMI (Manning) to be published in 2012. Another book about Windows PowerShell 3.0 will be published towards the end of 2012. 

Blogs:
Richard Siddaway's Blog: Of PowerShell and Other Things 
PowerShell for Windows Admins
IT Knowledge Exchange: Get Answers from Your Peers

I’m going to approach this as I would when producing a production script. The starting point is the requirements that we get from the event scenario.

Requirements

You are an analyst on the server team of a medium sized organization. You are studying the performance and reliability of various servers on the network and decide to produce a report that lists the number of errors from all the traditional logs on a particular server. The code you use should be capable of running against a local computer, or against an arbitrary number of remote computers.

Other requirements:

  • You will use impersonation for all remote connections, so you do not need to be able to supply credentials.
  • You should not display errors due to permissions or due to no events matching your filter.
  • You should not make changes to the users’ environment. If you do, you should change them back at the end of the script. Modification to the users’ environment following script completion will cost you points.
  • Your script should run without prompting against the local computer.
  • Your output should be organized such that the largest source of errors appears at the top of the output.

Design

When I start to design a script I note all of the features, issues and constraints I have to consider to fulfil the requirements. The parts in italics are the features I will build into the script.

My design notes:

  • Needs to run in Windows PowerShell 2.0 and 3.0. Therefore, use  #Requires –version 2 to set a minimum Windows PowerShell version for running the script.
  • Only working with traditional logs. Can use Get-EventLog. WMI is alternative, but it’s a bit more complicated.
  • Use an ErrorAction of SilentlyContinue to avoid displaying unnecessary error messages when accessing logs.
  • Remote Registry Service must be running on the remote machine. Need to be able to start it, save state, and reset at end of script.
  • Have to assume DCOM is available on the remote machine and that the firewall is configured to allow traffic. Could work round that by using CIM cmdlets in Windows PowerShell 3.0 in place of WMI and/or WSMAN cmdlets to read the event logs. Still a problem with starting remote registry service.
  • Won’t assume the Windows PowerShell remoting is available. It would make the answer much easier to write. In a production environment, could probably get remoting enabled as a default.
  • Must work remotely on multiple machines. Get-EventLog has –Computername parameter.
  • Output to include log name and number of events in Error category. Make Error default, but allow for other types. Use ValidateSet attribute on parameter.
  • Should include computer name in output. Use New-Object and set a custom type name.
  • Output object but think of how to use it for well formatted report. Show examples in Help by using Format-Table –GroupBy.
  • Test connectivity to remote machine, and provide warning if can’t reach it. Use Quiet mode on Test-Connection.
  • Provide a default computer name. LocalHost doesn’t work with Get-EventLog, so need to test and modify.
  • Sort BEFORE output so largest output first.
  • Work on pipeline, accept single or no computer or an array of computer names.
  • Need to add Help. Use comment-based Help.
  • Create as an Advanced Function so can use in a future module. Should always consider how to use scripts.
  • Use Write-Verbose and Write-Debug to comment.

The full script is shown at the end of the post, but I’ll discuss the major design points here.

The best place to check that you are running with elevated privileges is at the beginning of processing. Advanced functions have a BEGIN, PROCESS, and END blocks. The BEGIN block runs when the first object in the pipeline hits the function; PROCESS runs for every object, and END runs after the last object has been processed. Putting the privileges check in the BEGIN block means that we only do it once.

The ForEach ($computer in $computername ){…} block that starts the PROCESS block ensures that every computer is processed if we pass multiple computers to the ComputerName parameter.

I find that testing if a machine can be pinged is useful because it enables the machine to be skipped gracefully. If you want to test if it’s there, use the Count parameter set to 1. One answer is just as good as four in this case.

The Remote Registry service state is saved in a variable so that it can be used to set the service back to its original state. I need to use WMI to discover the service start mode, but that’s a Read-only property as far as WMI is concerned, so I need to use Set-Service to perform the modifications.

Getting a list of event logs on the remote machine and skipping those that are empty will speed processing. I always use Measure-Object to count things for me—it’s there, and I don’t need to worry about possible syntax issues.

I’m outputting objects, but I have sorted them for a given machine in descending order. If you are working with multiple machines using the GroupBy parameter with Format-Table gives a good display.

"dc02", "webr201", "server02" |

Get-EventEntryCount |

Format-Table -Property Logname, EntryType,

EntryCount -GroupBy ComputerName -AutoSize

The results are shown in the image that follows. Pipe it into a file and you have a report fit for any manager. I’ve added comment-based Help to the function. I always put my Help at the end of the function. It’s a personal preference because I prefer to go straight to the code.

Image of command output

What else could we do to this function?

  • Allow multiple entry types, such as Error and Warning
  • Use remoting (but would need to know or test that it is configured)
  • Run as a background job
  • Use  workflows in PowerShell 3.0

Tips

Here are some of things that I discovered, or tripped over, or that are just plain useful:

  • Use Write-Verbose and Write-Debug as comments in addition to supplying additional information when called.
  • Remote Registry service must be running to access log information on a remote machine.
  • Remember to use the ComputerName parameter when you want to access logs on remote machines

Working with event logs is explained in more detail in my books, PowerShell in Practice and PowerShell and WMI.

The Script

Here is the final script:

#Requires -Version 2

function Get-EventEntryCount{

[CmdletBinding()]

param (

 [parameter(Position=0,

   ValueFromPipeline=$true,

   ValueFromPipelineByPropertyName=$true)]

 [Alias("CN", "Computer")] 

 [string[]]$computername="$env:COMPUTERNAME",

 

 [parameter(Position=1)]

 [ValidateSet("Error", "Information", "FailureAudit", "SuccessAudit", "Warning", "All", "*")]

 [string]$eventtype="Error"

)

BEGIN{

  $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()

  $testadmin = (New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)

  if (!$testadmin){

   Throw "Must be run with elevated privileges"

  }

}#begin

PROCESS{

  foreach ($computer in $computername ){

    switch ($computer) {

     "."         {$computer="$env:COMPUTERNAME"}

     "localhost" {$computer="$env:COMPUTERNAME"}

    }

 

    Write-Verbose "Processing computer: $computer"

   

    $data = @()

    if (Test-Connection -ComputerName $computer -Count 1 -Quiet){

 

    Write-Verbose "Starting Remote Registry service on $computer"

    $origrrsrv = Get-WmiObject -Class Win32_Service -Filter "Name='RemoteRegistry'" `

    -ComputerName $computer

 

    if ($origrrsrv.StartMode -eq "Disabled") {

      Set-Service -Name RemoteRegistry -ComputerName $computer -StartupType "Manual"

    }

 

    if ($origrrsrv.State -ne "Running") {

      $origrrsrv.StartService() | Out-Null

    }

 

    Write-Verbose "Retrieving logs for $computer"

     

    Get-EventLog -List -ComputerName $computer |

    foreach {

      Write-Verbose "Processing log: $($_.Log)"

       

      $logcount = New-Object -TypeName PSObject -Property @{

         ComputerName = $computer

         LogName = $($_.Log)

         EntryType = $eventtype

         EntryCount = 0

       }

       $logcount.PSTypeNames[0] = "LogEntryCount"

       

       if ($eventtype -eq "All" -or $eventtype -eq "*") {

         Write-Debug "Processing all entries"

         

         $logcount = $_.Entries.Count

       }

       elseif ($_.Entries.Count -gt 0) {

         Write-Debug "Processing event type $eventtype"

         

         $n = Get-EventLog -LogName $($_.Log) -EntryType $eventtype -ComputerName $computer `

       -ErrorAction SilentlyContinue

         

         if ($n -ne $null){

           Write-Debug "Entries found"

           $logcount.EntryCount =  $n | Measure-Object | select -ExpandProperty Count

         }

       }  # end if entries

       else {

         Write-Verbose "$($computer): $($_.Log) is empty"

       }

        $data += $logcount

 

        if ($origrrsrv.State -eq "Stopped") {

          $origrrsrv.StopService() | Out-Null

        }

 

        if ($origrrsrv.StartMode -eq "Disabled") {

          Set-Service -Name RemoteRegistry -ComputerName $computer -StartupType "Disabled"

        }

 

     } # end of log processing foreach

   }

   else {

     Write-Warning "Cannot contact $computer"

   } # end if ping

   Write-Output $data | sort EntryCount -Descending

 } ## end computer foreach

}#process

END{}#end

 

<#

.SYNOPSIS

Counts the number of entries of a given type

in the event logs of a system

 

.DESCRIPTION

One or more computers - from pipeline or parameter -  are

accessed to read the envent logs and count the entries of a

given type. Empty logs are tested and the count is set to zero

 

.PARAMETER  computername

Name of computer for which log information

is to be retrieved

 

.PARAMETER  eventtype

Log entry type to count.

Accepted values are -

"Error", "Information", "FailureAudit",

"SuccessAudit", "Warning", "All", "*"

 

.EXAMPLE

Get-EventEntryCount

 

Accesses logs on local machine. Peforms default display

 

.EXAMPLE

Get-EventEntryCount -computername "." |

Format-Table LogName, EntryCount -GroupBy ComputerName -AutoSize

 

Accesses logs on local machine. Format display and group by computer

 

.EXAMPLE

"dc02", "webr201", "server02" |

Get-EventEntryCount |

Format-Table -Property Logname, EntryType,

EntryCount -GroupBy ComputerName -AutoSize

 

Accesses logs on remote machines. Computer names accepted from pipeline.

Format display and group by computer

 

.EXAMPLE

Get-EventEntryCount -computername "dc02", "webr201", "server02" |

Format-Table -Property Logname, EntryType, EntryCount -GroupBy ComputerName -AutoSize

 

Accesses logs on remote machines. Computer names accepted as array.

Format display and group by computer

 

.INPUTS

Computer name - string or string array

Envent type - string. Must be member of set

 

.OUTPUTS

Returns a LogEntryCount custom object with

properties:

ComputerName - name of computer

LogName - name of log

EntryType - Type of log entry

EntryCount - count of entries

 

.NOTES

 

 

.LINK

 

#>

 

}

~Richard

2012 Scripting Games Guest Commentator Week Part 2 will continue Monday when we will present the scenario for Event 6. Join me tomorrow for a wrap-up blog about the 2012 Scripting Games.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • The script proposed by Richard Siddaway doesn't meet the requirement that states:

    "Your output should be organized such that the largest SOURCE of errors appears at the top of the output."

    He probably didn't watch enough carefully the image added by the ScriptingGuy on

    blogs.technet.com/.../2012-scripting-games-advanced-event-5-list-errors.aspx where we can see 'ProviderNames' and their error count.

    Judges are also human and I also like when they fail ;)

    By the way, Richard's Design notes are excellent and should be an example to follow while assessing what has to be done before typing a single line of code.

  • Well spotted

    I perfer to think of it as artictic license though :)

    When I read the event I interpreted SOURCE as meaning log.

    When judging the event I have taken SOURCE to mean the event log source to meet the letter of the requirements

  • I've put a revised script up here

    msmvps.com/.../scripting-games-2012-comments-10-advanced-event-5.aspx

  • Wonderful explanation and the design notes are very detailed; I wish I could be that detailed when designing solutions. It has been a wonderful experience participating in the scripting games as I found a lot of interesting things to learn.