Art Beane

Explore articles and content from this author

Art Beane

7 articles published

2 min read

Notes for Event 6

When I read the instructions for event 6, I thought that here’s a tough one. A lot of competitors won’t have access to a test environment with Windows Server 2012 and Virtual Machines that they can actually work with. So, I expected that many of the entries wouldn’t get tested and intended to forgive minor errors that would have shown up in testing.
Well, there was one thing that really surprised me. The instructions were quite clear about minimizing “Are You Sure” queries to the user, but you can count on one hand the number of entries that included -Confirm:$false. This is just an example of why it’s so important to read the problem statement very carefully and extract the solution requirements. Then, after creating the solution, go back and verify that the requirements have all been met. Many of the entries called out this requirement in the comments, but then didn’t account for it in the script.
I had mentioned in a previous blog entry that, particularly in the advanced entries, the author was working too hard. Sometimes this means putting more emphasis on “completeness” than in solving the problem. Here’s an example of a wasted effort. A few entries used the _[ValidateNotNullOrEmpty()]_ test for a possible alternate to the default value for “Server”.  Because there is a default value for the parameter, it won’t be null or empty making this test unnecessary. Here, give this a try:

2 min read

Notes on Event 5

Into the home stretch and the entries just keep getting better! The only advice I’d like to offer this time is to be careful to read the instructions carefully. They included the specific folder where the files were located and I noticed several misinterpretations in the scripts. Some included a mandatory Path parameter and others had a default Path that was not the specified folder. Including an optional Path with the correct default would certainly be acceptable, but not those variations.
The instructions also included some ambiguity about what the log file actually contains. Was the client IP address in the first column (as specified in the instructions) or in a different column (as presented in the example logs)? There were a number of entries that just searched the logs for IP addresses and returned all of them. This approach would not be able to distinguished between the client and server addresses, which would give a wrong answer. Another approach searched for the “c-ip” column, but this would only work if the log files were as in the samples. Another method, select the second IP address in a line would also only work on the sample log style. There weren’t many entries that supported both file types, but one of them did it in a very concise manner, checking the first and ninth columns for an IP address and selecting the correct one.
Most of the entries used Sort-Object -Unique or Select-Object -Unique to eliminate duplicates, which was the first approach that I thought of. There were several entries, however, that used alternate methods that I thought were quite clever applications of PowerShell technology: hash tables with the IP address as the key, and Group-Object on the IP address. Both options provided a fairly simple way to also report the instance count for each address.
Returning an instance count sounds like an interesting option, but after thinking about it some more, I’m not so sure. Counts of the number of sessions and the hits per session would be much more interesting than the raw hits count. But that’s way, way beyond the scope of this event.
Anyway, just one more event to go. I’m expecting a spectacular finish!

3 min read

Judge notes for Event 4

 Wow! That’s the only word I can think of to describe the submissions this time. I’m really impressed with the approaches taken to solve this problem. The only thing that could have been better is quitting when the ActiveDirectory module or the Quest snapin weren’t found. I chalked that up to not having experience with an actual audit where no answer is not acceptable, so I didn’t count against it when evaluating the scripts. But, on this point kudos to the one script that tested for the AD module, then the Quest snapin, and fell back to the ADSI accelerator if neither were found.
Beginner entries
For me, the best entries were those that had the shortest pipelines. Those of you who used Get-Random -Count 20 -InputObject (Get-ADUser…) | Select … | ConvertTo-Html | Out-File had the shortest. And those who used Get-ADUser | Get-Random -Count 20 were a close second.
A couple of entries had something that at first I thought was silly. But, instead, it offers a learning opportunity. Here’s the code fragment: Get-ADUser -Filter {ObjectClass -eq ‘User’}. Paying attention to what the cmdlet does saves a lot of typing, not only here where the filter is redundant, but also when entering other parameters. For example, a similar extra effort occurs when default properties are explicitly listed in a -Properties parameter.
Advanced entries
As mentioned, the best entries were those that fell back to the [ADSI] accelerator when the AD module or the Quest snapin weren’t found. Making this kind of check and fallback is pretty important when responding to audit requests. This reminds me of a case where I actually had to respond to an audit request with the actual last logon date in a domain with mixed W2K3, W2K8, and W2K8R2 domain controllers. The default choice was to use the AD module, but since we had to check each domain controller (there were 72 of them), it turned out to be a real pain determining which method to use on each of them. In the end, we decided to install the Quest tools on the audit server and just avoid the issue.
There were several different methods used to verify the presence of the AD module before trying to load it. Most of them were actually more work that really necessary. The reason for this is that the Import-Module cmdlet does not return an error if the module has already been loaded. Thus, the easiest test would be:

2 min read

Judge notes for event 3

This event’s entries are impressive. Scoring appears to be higher than in the earlier events, so this one must have been easier to solve. So this time, instead of talking about good and bad scripts, I’m going to comment on some of the techniques I saw.
There was some “conversation” over whether Win32_Volume or Win32_LogicalDisk was the better approach to take. Fact is, either will return the requested data. So it really doesn’t matter which one you use. The controversy seemed to include misreading or misunderstanding the requirement of reporting on “local hard drives”, which implies that you need to use -Filter “DriveType=3” (or equivalent) with either to eliminate network or CD/DVD drives.
When passing a Path parameter into a function, it’s a good practice to include _[ValidateScript ({Test-Path -PathType Container})]_ in the definition to avoid having a file name passed in error. Doing the existence test for the path and creating it if necessary in the Begin section of the function would save some time over the various techniques used in the Process section.
One thing to remember when using a CIMSession is to close it when you’ve finished using it. A couple other points to pay attention to include accounting for the DCOM/WSMAN options when looking at remote computers and including _#requires -version 3_ in scripts that might be run by other people on computers that might not have PowerShell 3 installed.
Using a REGEX to validate a string parameter, such as a computer name, isn’t a bad idea, but it’s important to understand exactly what the match string means. As an example, some of the match strings included a pattern like this: "[a-zA-Z0-9.-]". This means all lower and upper case letters, any numeric digit, any character, or a minus sign. The any character (".") defeats the whole purpose of the match. It really should have been escaped to “.” to mean a period. This error would probably never appear due to the unlikelihood of a badly formatted computer name being fed into the function.
Lastly, a caution when including an optional credentials parameter. It’s probably not a good idea to default it to an empty credential object ($Credential = [System.Management.Automation.PSCredential]::Empty). If you do a _if ($Credential) {}_ call later in the script, it will always be $true and you may end up calling for the user to enter credentials far too many times. A better solution would be to check PSBoundParameters to see if a credential object was passed in.
Hope these ideas help. Good luck in Event 4.

3 min read

Some notes on Event 2 Advanced

I hate to seem negative, but I’ve noticed a few things about a number of the advanced entries that seem like folks didn’t read the instructions, or just weren’t careful about details.
There were a surprising number of entries that had [string]$ComputerName instead of [string[]]$ComputerName in the params section and then went on to treat the parameter as if it were an array.

  • Somewhat related to the array issue, the problem statement indicated that there could be several files that had computer identification for piping into the solution. Several scripts went beyond the minimum by accepting a filename property to process those files directly. I don’t think that extension is out-of-bounds, but  scripts that accepted only filenames and excluded ComputerName input didn’t get my vote.
  • The instructions asked for a “full help display”, but many of the entries had fairly limited documentation. One thing I especially missed was a .PARAMETER description.
  • My last negative comment is about parameter names. Although there’s nothing in PowerShell to prevent it, best practices in parameter names should be followed. The parameter ought to be $ComputerName, not $Name, $Server, $Computer, etc. I know it’s easier with verbs and nouns because of the Get-Verb and Get-Noun cmdlets, but please pay attention to how you name your parameters.

On the whole, though I really liked the effort everyone put into their scripts. Those that exactly met the requirements were short, sweet, and to the point. There were several extensions that I also liked.

2 min read

Notes on Beginner Event 2

 First of all, congratulations! It looks to me like a lot of learning is going on; the 2nd event entries look really good to me. I especially liked the way a number of you built up a one-liner by starting with a_ Get-WmiObject Win32_ComputerSystem -ComputerName (Get-Content file.txt)_ and piping it into Select-Object to generate the data. However, there were a couple of areas within the Select block that make me think that some more discussion of what $_ means in a pipeline would be helpful.
Within the Select block, it is necessary to make a call to Get-WmiObject Win32_OperatingSystem to get come additional information. It looks like everybody got the format correct: @{Name=‘OS’;Expression={Get-WmiObject}} where folks got into trouble was in specifying the ComputerName property. Some didn’t even include it, meaning that the OS value would be taken from the local computer and not the remote one. But, more often than not, the code contained a plain $_ : @{Name=‘OS’;Expression={(Get-WmiObject Win32_OperatingSystem -ComputerName $).Caption}}. So, what’s wrong with this? The problem is the value of $ at this point in the pipeline.
Let’s try an experiment to show what I mean. Try this:

3 min read

Judge Notes for Event 1

 A lot of you have been working too hard at solving the problem (both beginner and advanced). Some of this is clearly related to trying to offer a very complete solution but some look like attempts to write extra clever or elegant code. In the “real world”, there"™s probably not enough time or interest in putting lots of effort into these extras. The minimum it takes to achieve the goal is most often good enough. Here are a couple of examples to illustrate this (with the intent of providing a learning opportunity).
Working with the destination folder address.
A common error here was missing the subdirectory. Most folks got this correct by using some version of $.FullName.Replace("˜C:\Application\Log"™,"™\NASServer\Archives"™)_ or Join-Path “˜\NASServer\Archives”™ $.Directory.Name_, but there were a number who just used the root destination folder name without looking for the subfolder. And some others had solutions that (although I thought were innovative), took too much effort. Among them are: