Scripting Games 2013: Event 5 Notes

With week 5 in the books, I can see that everyone just continues to grow and show some great submissions. Of course, nothing is perfect and can always show areas of improvement, but trust me, you are all doing an excellent job!

I was hoping to have this article completed prior to now, but between a flight to Tech Ed and forgetting my power cord for the laptop, I am just now getting this accomplished. Better late than never Smile. Luckily, my fellow judges have put together some amazing blogs for this event that you should definitely review as well. So lets get to some of the things that I did and didn’t like.

What I liked

I liked seeing those people that were using Select –Unique or even Sort –Unique to pull out all of the duplicate IP addresses that were in all of the log files. By doing this, it becomes easier to identify all of the IPs rather than trying to figure out which ones are unique manually.

I was intrigued by those you used Import-Csv and then parsing the data with [ipaddress]. Very cool stuff! In fact, one of my tips on the PowerShell Magazine mentioned using [ipaddress] to validate an IP address.  So kudos to those of you who went this route! It shows just how many possibilities there are in solving a given requirement.

 

What I didn’t like

I have seen quite a bit of people using Get-Content to pull in the file data and then piping that into Select-String. While this does work, it isn’t exactly the most efficient way to accomplish this. Instead you should use Get-Content with the –ReadCount of 0 to pull the data as one string to work with. Either way will get you better off.

If you are going to use a regular expression for IP addresses, don’t use the following:

\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\

Ok, I am not talking about how this really doesn’t help for invalid IPs, but then again, we are not validating IP addresses, just looking for them. The problem is that this could actually be shortened down because ideally we are looking for the same items 3 times in a row. Instead take a look at doing something like this:

(\d{1,3}\.){3}\d{1,3}

As we are now down to voting for Event 6, I want to say “Great job!” to everyone that has submitted scripts for 1, 2 or all of the events. I promise that this will do nothing but make all of us better PowerShell scripters by reviewing everyone else’s submissions and by leaving constructive feedback.

Posted in 2013 Scripting Games Judges Notes, powershell, Scripting Games 2013 | Tagged , , , | 1 Comment

Guest Post On Hey, Scripting Guy! on Using The UpdateServices WSUS Module

My latest guest spot on Hey, Scripting Guy! is live today! I continue my article on managing the new WSUS server on Windows Server 2012 using the UpdateServices module. I go through each of the cmdlets that are available and demo how to use each one. Check it out and let me know what you think!

http://blogs.technet.com/b/heyscriptingguy/archive/2013/05/27/use-the-updateservices-module-to-manage-wsus.aspx

Posted in Modules, powershell, WSUS | Tagged , , , , | Leave a comment

Scripting Games 2013: Event 4 Notes

It is all downhill from here folks! Event 4 is in the books and we only have 2 more to go! Everyone has been doing an outstanding job with their submissions and it is becoming clear that people are learning new things and showing some great techniques with their code.

Of course, this doesn’t mean that there isn’t room for improvement with some submissions to make them even better or just some simple mistakes that can be cleaned up to make average submissions into amazing submissions. With that, its time to dive into my notes…

Send-MailMessage, not Net.Mail.MailMessage

I’ve seen several submissions where people have an optional email message that can be sent out. I had no issues with this and in fact applaud those that did! There is nothing wrong with added functionality to a script to make it better. What I did have issues with was those that chose the hard road of building out the email system using .NET instead of the readily available Send-Mailmessage cmdlet.

Compare these examples:

.NET Way

#SMTP server name
$smtpServer = $smtpServer
 
#Creating a Mail object
$msg = new-object Net.Mail.MailMessage
 
#Creating SMTP server object
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
 
#Email structure
$msg.From = $EmailFrom
$msg.ReplyTo = $EmailFrom
$msg.To.Add($EmailTo)
$msg.subject = $EmailSubject
$msg.attachments.add($Attachment)
$msg.body = $EmailBody
 
#Sending email
$smtp.Send($msg)

PowerShell Cmdlet

$email = @{
    To = $EmailTo
    From = $EmailFrom
    Subject = $EmailSubject
    Body = $EmailBody
    SMTPServer = $smtpServer
}
Send-MailMessage @Email

The proof is not only in the lines of code, but also in the readability of the code.

Again, don’t use .NET types unless absolutely necessary as it creates more work for you and really goes against the best practice of using PowerShell cmdlets in PowerShell scripts. Please try to force yourself away from this habit.

A better [ValidatePattern()] with [ValidateScript()]

I didn’t count this against anyone, but I have seen a lot of people using [ValidatePattern()] to do some regular expression (RegEx) matching. While it does work in preventing incorrect data from being supplied to a parameter, the error message is something that is just terrible for a user to try and interpret. Take this example:

Function Test-Something {
    [cmdletbinding()]
    Param(
        [parameter()]
        [ValidatePattern("^(\d{1,3}\.){3}\d{1,3}$")]
        [string]$IPAddress
    )
    $IPAddress
}

If a wrong value is given…

image

The false IP fails the test, but unless you know RegEx, the error message pretty much means nothing at all. Enter [ValidateScript()] to make this better. The one thing to watch out for with this is that you provide more to the scriptblock than this:

Function Test-Something {
    [cmdletbinding()]
    Param(
        [parameter()]
        [ValidateScript({($_ -match "^(\d{1,3}\.){3}\d{1,3}$")})]
        [string]$IPAddress
    )
    $IPAddress
}

image

While the idea is there, it still doesn’t help the user to know what is wrong. Instead, add and If/Else block to handle the matching and use Throw to put a more descriptive error message.

Function Test-Something {
    [cmdletbinding()]
    Param(
        [parameter()]
        [ValidateScript({
            If ($_ -match "^(\d{1,3}\.){3}\d{1,3}$") {
                $True
            } Else {
                Throw "$_ is not a valid IPV4 address!"
            }
        })]
        [string]$IPAddress
    )
    $IPAddress
}

image

Now the error message has more meaning to it!

User the –Properties Parameter for Get-ADUser

As with Get-WMObject, you need to use the parameters available in the cmdlets to have the most efficient submission. If  you specify –Properties * and then try to pull data, you are bringing back more data than needed. Instead use –Properties and specify each property that you want.

LastLogon or LastLogonTimeStamp property?

This one would take some research to determine the best course of action. While either one will get you the last logon date required for the submission. You have to consider which is the best approach. You can use the LastLogonTimeTimestamp but under the knowledge that it is only replicated every 9-14 days. The other option is that you can use LastLogon instead which is not replicated to all Domain Controllers meaning that in order to accurately determine the accurate timestamp of the user account, you must query each of the domain controllers in the environment and take the highest value found. Some people did this, others took a different route. Here are my points on each one:

  1. LastLogonTimeStamp
    1. 6-14 days to replicate
    2. May not be the most accurate
    3. Best performance as only one Domain controller needs to be queried
    4. Ok if accuracy isn’t the top priority and timeline is small to get this done
  2. LastLogon
    1. Does not replicate at all
    2. Most accurate
    3. Requires querying every domain controller and finding the largest value
      1. Performance hit
    4. Ok if accuracy is important and time isn’t an issue

So what does this mean? Well, it really depends on the scenario. If someone did go for the lastlogon attribute, I hope that they knew to hit all of the domain controllers, otherwise the report will be suspect as the data will not be that accurate.

Use of Here-Strings for HTML code

I’ve seen more people using Here-Strings instead of concatenating strings when building out their html /CSS styles for use with ConvertTo-HTML. And for that I say Good Job! I still see some people trying to concatenate strings and hope that those habits change for the last 2 events (if applicable). I did see somebody use a stringbuilder class to construct the html code. Very interesting and I can’t say that I really have much of an opinion on it as it is something different and the person does use it beyond the initial creation of the styles.

All in all, great job with this event! I am looking forward to seeing what everyone brings to the table with Event 5!

Posted in 2013 Scripting Games Judges Notes, powershell, Scripting Games 2013 | Tagged , , , , | 6 Comments

Scripting Games 2013: Event 3 Notes

Wow, it is hard to believe that we are now halfway through the Scripting Games! As the events have progressed, I have seen a lot of improvement with the techniques as well as seeing new techniques that continue to impress me. On the flip side, I have seen some mistakes or assumptions when coding that cause a potential 5 star script to be a 2 or 3 star script. The best part about all of this is that we are all (yes, even the judges) learning new things that can only help to improve everyone’s scripting knowledge.

With that, it is time to take a look at the things that could be improved upon or should be avoided altogether.

-Filter vs. Where-Object

This was probably the biggest issue I have seen with the submissions for Event 3. Too many submissions were taking the output of Get-WMIObject and piping the results into Where-Object to get all of the local hard drives. The sooner you get used to working with –Filter, the better off you will be working with scripts and one liners to query WMI on both local and remote systems. When you use the Filter parameter, especially on remote systems, the filtering is performed on the remote system and only the objects that you filtered for will be passed back to the local system.

Some people claim that it is an ugly approach and I disagree. If I am looking for only hard drives, which one looks worse:

Filter

drivetype=3

Where-Object

$_.drivetype –eq 3

To me, the obvious answer is using Filter and supplying the WQL statement. Sure the statements may get complicated, but so could using the Where-Object. And if you might not be sure as to what type of WQL statement to use, simply look it up. There are plenty of resources available that you can use to figure out the proper query.

I’ve also heard that performance was the reason for using Where-Object locally vs. –Filter and that it isn’t much better to consider using the –Filter parameter. Lets compare performance just to see.

Filter

(Measure-Command {
    Get-WmiObject -Class Win32_LogicalDisk -Filter 'drivetype=3'
}).TotalMilliseconds

image

 

Where-Object

(Measure-Command {
    Get-WmiObject -Class Win32_LogicalDisk | Where {
        $_.driveType -eq 3
    }
}).TotalMilliseconds

image

About a 50 millisecond difference. Pretty good difference to me. Some might disagree, but the fact is that using –Filter has better performance. So why is that the case? Let’s take a closer look at each command. If you know PowerShell, you know that Where-Object takes the input from whatever command is before it in the pipeline and starts performing a comparison against the object in the pipeline based on the data you provide.

Get-WmiObject -Class Win32_LogicalDisk | Where {
    Write-Verbose $_ -Verbose
    $_.driveType -eq 3
}
 

image

As you can see, each object is being looked at when just using Where-Object. I wonder what happens when I use the –Filter parameter…

Get-WmiObject -Class Win32_LogicalDisk -Filter 'drivetype=3' | Where {
    Write-Verbose $_ -Verbose
}

image

Instead of going through 4 items, I only see the 3 that are actual hard drives. This is the efficiency of using a parameter on ANY cmdlet vs. using Where-Object.

So bottom line: –Filter is not only a best practice for Get-WMIObject, but it is also more efficient and provides better performance.

Hard Coded Paths

Something that was driving me crazy during this event, especially on the beginner side was the use of hard coded paths to output the html file. When you use a path such as C:\smith\awesomefolder as the directory, you cannot expect it to work for me. I shouldn’t have to modify your code to make it work for me. It should work without any issues. The solution for this would be to use something like $pwd, or also in some scripts I saw (good job, by the way!), $Env:TEMP  or $Env:USERPROFILE. There are other possibilities, but the main thing is that you must make sure that it is universal to the everyone who uses it.

.Net Types

This is the PowerShell scripting games, not the .Net games. Don’t use [DateTime]::now, when there is a perfectly good cmdlet called Get-Date that will accomplish the same thing. I even saw this: [environment]::MachineName being used. Really? You can get the same thing out of $Env:Computername.

Another person took the time to create their own network checker using System.Net.NetworkInformation.Ping. The same thing can be done by using Test-Connection.

Unless there is compelling need to use this (haven’t seen it yet) to use a .Net type to accomplish your goal, stick with the cmdlets and you will be better off.

Over Commenting

This is pretty self explanatory. We don’t need a 2-3 line comment talking about how you are querying WMI on a local system and then continuing this trend for each and every line of code. Not every line of code requires a comment, let along multiple lines of commenting. Keep it simple and clean and everything will be Ok.

Some Final Points

  • Consider use of [pscustomobject] vs. Net-Object PSObject for V3 code
  • Stop using back ticks as line breaks.
  • If the event says to do a one line, then you should shoot for a one liner and avoid using semicolons because it is no longer a one liner.
  • No more concatenating (Ex: “this” + $something + “is great!”). We are not in the vbscripting games.
  • Also avoid using += to append lines to an existing variable. Use a Here-String instead.

Keep up the great submissions and I am looking forward to seeing what you do with Event 4!

Posted in powershell, Scripting Games 2013 | Tagged , , , | 4 Comments