New Version: Simple Server Status
Had someone at Spiceworks ask a question about a script they were trying to use so I asked them to post the relevant code and, lo and behold, it was my Simple Server Status script! He was having a problem with authentication because the ID he was running the script under didn’t have rights and he needed to run the script with his domain level credentials. Never got an answer as to why he couldn’t just run the script with those credentials, but modifying the script to use alternative credentials should be easy enough. Of course, it never is.
2013 Scripting Games
This is the first year I’ve decided to enter into the Scripting Games, formerly run by Microsoft now being run by Powershell.org. As much as it pains me to say it, my first submission didn’t do too well but it got me thinking about things differently–which is really the whole point of the games. Winning is cool and all, but learning is the goal.
What does this have to do with the Simple Server Status script? It just happens to be the first script I looked at after receiving some comments on my event 1 submission, so while I’m in the script not only do I want to add the ability to use alternative credentials but I also want to do some error handling too. Boy, scope creep before I can even get PowerGUI opened!
One thing about Simple Server Status was that the intention is for it to run as a scheduled task, which means we can’t just use Get-Credential to get those credentials, nor am I willing to save the username and password in clear text so that means we need to bust out our old friend, Secure Credentials Function. I’ve used this function a couple of times now and it works well but I noticed something right away that would be a potential problem. The function always assumed username/password, never domain\username and password and, potentially, I could see someone needing the domain information–what if you were an admin to several child domains? You can’t really save the password with the domain and username embedded too, although I saw one instance of someone using an XML file for this–but frankly I didn’t want to go down that road. So the simple answer was since I was saving the file as the username, why not embed the domain in there too? Can’t use the backslash character so have to replace that with a tilde (~) and then put in some logic to convert it back out:
$AuthUser = (Split-Path $File -Leaf).Substring(12).Replace("~","\") $AuthUser = $AuthUser.Substring(0,$AuthUser.Length - 4)
I’m able to use some fixed numbers in the first Substring because I know exactly where the name starts, as the first part is simply a fixed string I put in there to identify the file. Now, just add a -Credentials to my WMI searches and that part is all done. Except I immediately ran into a problem in testing. Turns out you can’t have alternative credentials if you run a WMI query against the local machine! That means we have to leave them out if we detect that we’re querying against the local machine, and we’ll just have to rely on the current credentials!
You could use an If statement here and write you Get-WMIObject cmdlet twice, which would be pretty easy, but this is a lot of repetitive work. I also don’t like it because if I have to make a change the WMI cmdlet, I have to change it in two places. Much better to take advantage of splatting and hashtables:
$CredParameter = @{ ComputerName = $Computer ErrorAction = "Stop" } If ($Computer.ToUpper() -notlike "*$($env:COMPUTERNAME.ToUpper())*" -and $Cred) { $CredParameter.Add("Credential",$Cred) }
Set parameters in the hashtables with values, than check if it’s the local computer and we’re even using alternative credentials (it’s still possible to run the script without them) and if so, add another key and value to the hashtable with the credential information. That’s it, we’ve accomplished the alternative credentials request and put a really nice spin on them. Now the credentials can be securely stored (well, mostly secure) in a file and we can continue to run the script in a scheduled task.
Error Handling
When using error handling, you always have to ask the question what you intend to do with the error when/if you find it. Do you want to simply store/log the error and keep on barreling through the script? Or do you want to stop in your tracks on not go any further? For this script, we need to log the errors. It’s running as a scheduled task so you’re not going to be sitting there watching the console if it errors out, therefore the errors need to be incorporated into the output.
To accomplish this part, I decided to surround my entire computer gathering portion of the script in a Try {} block. In the Catch {} portion–which is triggered if a stop condition is encountered in the script, which you’ll notice in the hashtable above, I have ErrorAction = “Stop”, which means when the script will halt if an error is encountered with the Get-WMIObject cmdlet–I simply set a status that there’s an error, then store the actual error in a variable. Luckily for us, Powershell is kind enough to store those errors in the $Errors variable. The latest error is always the first element in this System Array: $Errors[0]. I then decided to be thorough and clear the $Errors variable using the Clear method, which is available for this special variable. Not strictly needed, but I like to make sure I get the right information.
Catch { Write-Verbose "Error encountered gathering information for $Computer" $ErrorReport = $Error[0] $Error.Clear | Out-Null }
Now we know there’s an error, it was pretty simple to incorporate that into the custom HTML of the rest of the code. I won’t bore you with those details as they’re not too exciting but if you want to scan through the script then enjoy!
Pipeline
But I couldn’t just leave it here. The old script only had the Servers parameter as a way for you to specify which servers you wanted to monitor. That meant a text file or manually putting the server names in the parameter. What if you wanted something more dynamic? Something that queried Active Directory to find out the current set of servers (assuming you have some kind of naming convention that would lend itself to this kind of query). To do this, we need to activate the pipeline capabilities. That meant changing the name of my parameter to Name (so it matches the Computer Object that Get-ADComputer outputs) and configuring that parameter to accept pipeline information:
Param ( [Parameter(ValueFromPipeline=$true, ValueFromPipelinebyPropertyName=$true)] [Alias("Servers") [string[]]$Name = (Get-Content "c:\utils\servers.txt"),
Nothing too fancy here, just standard Parameter settings to activate the pipeline. I also added Servers back in as an alias to keep compatibility with older versions of the script. Then I had to add the Begin/Process/End blocks, which meant reworking quite a bit of logic. I put my functions and static HTML content in the Begin block and then for the Process block all I had the script do was gather all of the computer names into an array. The reason for this is I wanted the servers to be sorted by name, which means all of them have to be present in the array! So the Process block can’t be much simpler! The primary logic was then moved into the End block where the majority of the work is done.
But, I’m still not done. While I don’t subscribe to the “Every time you use Write-Host in a script, a puppy dies” philosophy, it does make sense for a script that is running in a scheduled task to not bother writing any output. No one’s there to read it! Therefore I decided to add Verbose support into the script. Luckily that was a simple matter of adding:
[CmdletBinding()]
To the top of the script, and then changing every Write-Host to Write-Verbose.
Ok, hands up! Walk away from the keyboard. Script is good, stop messing with it! Hope you enjoy!
[…] There is a new version of Simple Server Status and I talk a little bit more about it here. […]
Your script works great, however each time I run it, it has a cumulative effect (shows in the HTML file each time I’ve run it before). How do I get it so that each time it’s run, it’s a fresh take and doesn’t show the earlier info.
Hmmm.. not sure what you mean by cumulative? As I remember the script doesn’t save anything!
Yes, script has cumulative effect. It remembers past status. Run it if you don’t believe. I can’t find it in script.