For this post, I am going to look at my first PowerShell script I have written for a production environment. I had used PowerShell before and wrote small little scripts that ran interactively, but this was the first scheduled one and the first one I wrote completely by myself. I am going to look at what my way of thinking was and what I would have done differently if I had written the script today. Maybe if you are a beginner with PowerShell, these tips can help you with writing your first PowerShell script
But why?
Over the years, a lot of people have asked me how to get started with PowerShell. I always recommend to find something you want to accomplish and figure out how to do that. This script is an example of that. I found a use case, I worked out how to get there and I googled my script together. I found out how things worked while I was going and most importantly: I had fun with it.
And why look back? Because I think it’s good to see where you came from. I remember when I was just trying to figure out PowerShell, I had a lot of help of colleagues and I loved doing it. Today, I still often feel like I am figuring it out. I still discover new things. By looking back, I can see a bit of how my skills have improved through practice through the years.
Things to remember for your first PowerShell script
As I said, I think a great way to get started with PowerShell is to just start building. Google your way and play around until it works. I know not everyone agrees with me, because you are basically working with the language without a complete understanding. I still believe in this method, but there are some important pointers.
Test test test
Test your scripts. Please please test your scripts. There is a great example in this script. There is a line in the script where two files are removed:
What would happen if the variable $DFSRtestfile1 pointed to a different file than expected?
I can tell you what happens, because I did it. I ran that script and the variable pointed to a directory instead of a file. And that is the story of how I lost C:\Temp on my laptop. Imagine if I had been testing on the production server I was writing for.
A mistake is easily made and can have big impact when you are developing. So find a way to test outside of production, make use of WhatIf functionality and use Write-Output or Write-Host to show what would happen.
Get a review
Done with your testing and writing? Or completely stuck with no clue how to move on? Talk to people! Find someone who can help you out, or after you have finished your script, ask what they think. A lot of people that work with PowerShell love what they do and love to talk about it, so they will be happy to review for you. If you don’t have anyone to talk to at work, Check Twitter (use #PowerShell ) or the Discord/Slack channel. If I can help you, reach out in the comments, the contact form or on Twitter.
The script
The script I’m going to look at was written in 2016.
You can find the script here.
Some disclaimers are in order here:
- I had written some scripts with colleagues before (where they were typing and I was mostly observing) and I had used PowerShell interactively before. So this is not my start from zero
- This script worked with a monitoring tool and used a specific module to create output. I have replaced the cmdlets that called the module with Write-Output cmdlets that show what was done
- Some comments were written in Dutch, I have changed these to English. I left the emoji’s though 🙂
The problem
So what problem was I trying to solve? At the time, I was working with a customer that had DFS Replication between two file servers in different locations. With DFS replication, files should be replicated almost instantly (unless they are very large or there are a lot of them). In this customers case, we found replication sometimes stalled and we would not notice it. If this interfered with Backup schedules, the replication could fall behind and it would be a mess to fix.
So to make sure replication was working, I wanted to create some sort of monitoring.
We are talking server 2012 here, which means there were no PowerShell cmdlets for DFSR yet.
My solution
I decided on a very basic solution: I wanted to create a file in a folder on one of the servers. After that, the same folder on the other server would be checked. If the file is there, the script is working. The file should be removed to keep the server clean. If it doesn’t appear, than something is off. Monitoring is triggered and the file is kept for monitoring
My approach
So how did I start? First, I made a small overview of what I wanted to achieve. And I started searching online. I searched for the logic, but also for the syntax I needed.
I was lucky to find a batch script that did things that were very similar to what I needed (can’t credit because I can’t find it anymore). So that helped me a lot with the basic setup.
Note: I Created this schema for this blog post. I can’t remember if I did it for this script, but I did an do create little diagrams of what I want to achieve. I recommend doing this if you are creating scripts. As you will see, my rewritting script is still following this path.
You can find the script I created here. (If you want to copy something, focus on the script at the bottom of this page 🙂
What would I change?
If ($True -eq $true)
The first action in the script, is a check if both the paths exist and are accessible. This is a good move, it provides some basic error handling. But the way I am doing it is not very efficient. The old script handles it like this:
I see some problems here:
- First, the Test-Path results are put into variables. After that, it is tested if the result is $true. That can be done with less lines
- Secondly, the script is basically doing the same thing twice. Let’s see how we can change that.
A cleaner way is to put the two paths together in a Foreach loop:
Foreach ($Path in $DFSpath1, $DFSpath2) {
if ($false -eq (Test-Path $Path)) {
Throw "$Path doesn't exist or no permission to access this path"
}
}
As you can see, this is a lot shorter.
Another option is to put the paths in a parameter at the top of the script. When you do that, you can use validation to check for the paths before you even start.
param( [ValidateScript({Test-Path $_ })] [String]$DFSpath1, [ValidateScript({Test-Path $_ })] [String]$DFSpath2 )
I like this way the best, as it also shows that these variables can be different for different environments. My problem is that when I write it this way, the script has to be called with parameters. This would be a breaking change to the functionality of the script.
So for this case, I chose to keep the variables hardcoded in the script and perform the test with the Foreach loop. By using Foreach, it will be relatively easy to add more servers to the script. I can also use the loop to set the filenames, so I don’t have to do that twice.
The While Loop
I can’t take any credit for this part of the script, as is was almost literally taken from the Technet forum.
Note: If you do like me and copy a code snippet from the internet, put a link in a comment above the code. This might later help your colleagues to know why you made choices. In this case, I kept the variables from the example, which made it pretty easy to find the original page.
I do not like the while loop. Or to be more clear, I do not like that it has combined an If-loop and a while loop. As I feel this goes against what While can do.
So let’s consider the old code
What I want to replace this with, is a while loop that checks both for the time as if the path exists.
Plus, the stopwatch function is fun, but it doesn’t really add that much in this case. The Start-Sleep can handle the counting. How about this:
$i = 0
while (-not(Test-Path $DFSRtestfile2) -and $i -lt $Secondstillerror) {
start-sleep -seconds 5
$i = $i + 5
}
Looks a lot cleaner right?
Now I can see if the results with an if-loop and take care of removing the file there:
if ($i -ge $SecondsTillError) {
Write-output "Replication is NOT working"
}
else {
Write-output "Replication was successful"
Remove-Item -path $DFSRTestFile1
}
Note: If you have worked with DFS Replication before, you might see my other error in the script. I shouldn’t have to remove both files if replication is working. I only have to do it once and the replication will do the rest.
Comment-Help
Ah, the comment-Help. I think this was some kind of universal way of creating Comment Help back in the days, because I have seen it almost everywhere. The more hashtags, the better.
It does look more fun than the comment-help I have replaced it with.
But the great thing about using this format, is that the output will be visible when you use Get-Help on a function.
It also is a standard that is well known, So it is good to get used to it.
If you want to create a correct comment-Help, use Visual Studio Code with the PowerShell extension. By typing Comment-Help, a snippet will load.
Capital Letters
Something I have learned wrong and am still struggling with: I do not pay attention to Capital letters. Not in my cmdlets, not in my variables. The only thing you could count on was my inconsistency. You can bet it drove my coworkers crazy when doing reviews (Sorry Gerbrand).
Although it looks very sloppy when using PowerShell on Windows, there will be no concequenses for doing this. does not care about capitals. This measn I could get away with it for a long time. But now that PowerShell is multiplatform, this has actually gotten me into trouble.
I still do it wrong when I am coding fast and I actually have written scripts to fix my errors. But if you are starting out, make sure you start doing this correctly
The end result
So this is the script as I would create it now:
It has been fun to look back and look at my first PowerShell script.
I realize this script is probably not perfect. And there are other approaches. Mostly, I am confident that if I look at it four years from now, I am able to rewrite it again as I probably learned more. (will put a reminder in my calendar).
I hope this can inspire people that are starting out to get going with it. And if you have been using PowerShell for a while, you might want to look back and recreate your own first script :).
What’s the purpose of the $i in line 32…You add to it on line 40 and then set it to zero on 48…
Line 51; $i += 5
😉
Nice post!
Hey Chris, nice to hear from you, how are you? 🙂
The $i in line 32 and line 40 don’t actually have anything to do with the $i in line 48 and line 51. It might have been a bit clear if I gave them a different letter.
In both cases, $i is used in the loop to count upwards.
In the first loop, it is used to create the filename in line 39. By using $i, it is relatively easy to add more than two DFSR server paths to the script.
The first file is called DFSRTestFile1. In each loop, the file number increases by 1, thanks to $i++.
In the second loop, $i is used to count time. It starts at zero as set in line 48. After that it enters the while-loop. In line 49, you can see that the while loop checks (among others) if $i is more than the set error timeout. Every loop contains a 5 second sleep as well as adding 5 to $i. This way $i is simulating a stopwatch. If something is wrong with the DSF replication, $i will at some point exceed the time that was allowed for replication and break the while-loop. In line 55, it is checked if that was the case.
Hope this clears it up a bit.
Hi Barb,
I missed the $i in the FileName…shouldn’t read code on an iPhone ;). Everything is fine, thanks for asking.
You know why I think this is such a good post?
Because it shows that you can improve things. That means that it is worth checking on running scripts and code once in a while to see if you can make it better. And if you are able to do that, that shows you are getting better and growing, and that’s always good to know. Say hi to Sander for me okay ? and keep up the awesome blog!