
Windows PowerShell solution to Event 7 in the 2008 Winter Scripting Games.
This event tested your debugging skills. Were you able to start with a script that’s completely broken and make it work, without starting from scratch? The script we started with was supposed to do the following:
| • | Find all the text files (files with a .txt file extension) in the C:\Scripts folder and all its subfolders that were created more than 10 days ago. |
| • | Copy all those files to the C:\old folder. |
| • | Echo the names of all the files that were copied. |
| • | Echo the total number of files that were copied. |
Let’s start by taking a look at the broken script, then we’ll tell you what needed to be done to fix it:
for ($i in Get-ChildItem C:\Scripts)
(
if {($i.CreationTime -gt ($(Get-Date).AddMonths(-10))) and ($i.Extension = "txt")}
{
Copy-Item $i.FullName C:\old
$i.Name
$x = $x + 1
}
)
""
"Total Files: " + $y
We mentioned in the instructions that we had introduced 10 errors into this script. We’ll give you the list first, then walk through them one-by-one:
1. | Change for to foreach |
2. | Add -recurse After C:\Scripts |
3. | Change = to -eq |
4. | Add "." to "txt" |
5. | Put - In Front of and |
6. | Change AddMonths to AddDays |
7. | Change -gt to -lt |
8. | Replace {} with () in if |
9. | Replace () with {} in foreach |
10. | Change $y to $x |
Change for to foreach
The very first statement in this script is supposed to be a loop that loops through all the files in the folder C:\Scripts and its subfolders. Now, there is such thing as a for loop in Windows PowerShell, but typically when we read through a list or an array we use a foreach loop. And that’s exactly what we need to do here. Instead of for:
for ($i in Get-ChildItem C:\Scripts)
we need to use foreach:
foreach ($i in Get-ChildItem C:\Scripts)
Add -recurse After C:\Scripts
No, we’re not quite done with line 1 of this script yet; there’s another problem. Remember, we’re reading all the files in the folder C:\Scripts and its subfolders. By default the Get-ChildItem cmdlet will retrieve only the files within the specified folder. If we want to retrieve files from the subfolders (which we do) we need to add the –recurse parameter:
foreach ($i in Get-ChildItem C:\Scripts -recurse)
Change = to –eq
In Windows PowerShell, assignment operators are not the same thing as comparison operators. For example, this statement assigns the value 4 to the variable $a:
$a = 4
Now let’s suppose we want to take some action if $a is equal to 4. You might try to put this in an if statement:
if ($a = 4)
This line will run with no errors. The problem is that the condition ($a = 4) would evaluate to true every time, no matter what the value of $a had been. Why? Because we’re not checking to see if $a is equal to 4, we’re assigning the value of 4 to $a. Because of that, not only will the condition always be true, but after running the if statement $a will be equal to 4, no matter what it had been before the if statement. If we really want to check whether $a is equal to 4 rather than simply set $a to 4, we need to use the –eq operator:
if ($a –eq 4)
And that’s what we need to fix in our script. Take a look at this if statement:
if {($i.CreationTime -gt ($(Get-Date).AddMonths(-10))) and ($i.Extension = "txt")}
In particular, take a look at the last part:
$i.Extension = "txt"
We’re trying to find out if the file we’re looking at is a text file, meaning it has an Extension of txt. Instead we’re trying to set the Extension property to txt. We can fix this statement by replacing the equal sign (=) with the –eq operator:
if {($i.CreationTime -gt ($(Get-Date).AddMonths(-10))) and ($i.Extension -eq "txt")}
Add "." to "txt"
Before we leave this part of the script, take another look at the statement we were just working on:
$i.Extension -eq "txt"
We’re checking to see if the Extension property has a value of “txt”. The problem with this is that even though we’ve fixed the operator we still won’t find any files that meet this condition. Why? Because the Extension property returns the file extension of the file, including the dot (.). So rather than checking for an extension of “txt” we need to check for “.txt”:
$i.Extension -eq ".txt"
Put - In Front of and
Uh oh, there’s still a problem with that if statement. Let’s take another look at it:
if {($i.CreationTime -gt ($(Get-Date).AddMonths(-10))) and ($i.Extension -eq ".txt")}
We’ve fixed the problem with the equals operator, but there’s something else. Before explaining the next problem, we’re going to stop for a moment and break down this if statement. It’s pretty complicated, so it will probably help if we go over what’s happening one step at a time. Here’s the first part of the statement:
($i.CreationTime -gt ($(Get-Date).AddMonths(-10)))
All right, let’s break it down just a little more:
$i.CreationTime
Here we’re simply reading in the CreationTime property of the file. Next comes –gt, which is the comparison operator “greater than.” (If you’re already seeing more problems, congratulations. We’ll get to those in a little bit.) We checking to see whether the file’s CreationTime is greater than the last part of the statement:
($(Get-Date).AddMonths(-10))
First we’re calling the Get-Date cmdlet. This cmdlet returns the current date. Once we have the current date we call the AddMonths method, passing it -10, meaning subtract 10 months from the current date. Back to the full statement:
($i.CreationTime -gt ($(Get-Date).AddMonths(-10)))
As you can probably figure out now, we’re checking to see whether the file’s CreationTime is greater than the date 10 months ago.
On to the next part of the if statement:
and ($i.Extension -eq ".txt")
In addition to comparing dates, we also want to make sure the file has a txt extension. So our if statement checks to see whether the date is in the range we’re looking for, and if the extension is txt. The first problem here? The and statement. The syntax for an and statement in PowerShell is –and. Once we fix that, the if statement looks like this:
if {($i.CreationTime -gt ($(Get-Date).AddMonths(-10))) -and ($i.Extension -eq ".txt")}
Change AddMonths to AddDays
Remember, as we were walking through the if statement, we mentioned that we check the date 10 months ago? According to the event instructions, we don’t want to check files against a date 10 months ago, we want to check against a date 10 days ago. That means that rather than calling the AddMonths function, we should be calling the AddDays function:
if {($i.CreationTime -gt ($(Get-Date).AddDays(-10))) -and ($i.Extension -eq ".txt")}
Change -gt to –lt
Yes, there’s another problem with the if statement. In addition to comparing days rather than months, we also wanted to find all the files that were more than 10 days old. That means that the CreationDate would have to be less than the date 10 days ago, not greater than. That means we need to change –gt (greater than) in our if statement to –lt (less than):
if {($i.CreationTime -lt ($(Get-Date).AddDays(-10))) -and ($i.Extension -eq ".txt")}
Note. It’s pretty amazing what can go wrong in one line, isn’t it? If you start running into a lot of problems, it might be worthwhile to break this into multiple statements. Something like this:
$ct = $i.CreationTime
$dt = Get-Date
$pastDate = $dt.AddDays(-10)
if ($ct –lt $pastDate)
{
if ($i.Extension –eq ".txt")
{…}
}
This is quite a few more lines of code, but it does the same thing and might be easier to keep track of what’s happening. But that’s entirely up to you. |
Replace {} with () in if
Believe it or not, there’s still one more problem with the if statement. Let’s see what it looks like right now, after we’ve corrected several of the problems:
if {($i.CreationTime -lt ($(Get-Date).AddDays(-10))) -and ($i.Extension -eq ".txt")}
This is almost what we need, but not quite. The syntax for an if statement in PowerShell is this:
if (condition)
{
code to run if condition is true
}
Take a close look at the parentheses and curly braces. It’s very important to put parentheses where PowerShell expects parentheses and braces where it expects braces. The script won’t run correctly if you get these all mixed up. And that’s exactly what we did in our if statement; we surrounded the condition statement with curly braces rather than parentheses. Just change this:
if {…}
to this
if (…)
So the final, working if statement looks like this:
if (($i.CreationTime -lt ($(Get-Date).AddDays(-10))) -and ($i.Extension -eq ".txt"))
Replace () with {} in foreach
Speaking of parentheses and braces…looks like we did it again, but this time on the foreach loop. The syntax of a foreach loop looks like this:
foreach ($i in @a)
{
code to run inside the foreach loop
}
The foreach loop in our broken script (after fixing the problems we talked about earlier) looks like this:
foreach ($i in Get-ChildItem C:\Scripts -recurse)
(
…
)
Change the parentheses surrounding the script block to braces to fix this problem:
foreach ($i in Get-ChildItem C:\Scripts -recurse)
{
…
}
Change $y to $x
Each time through the loop, whenever we found a text file we copied the file, displayed the name of the file, and incremented a counter to keep track of the number of files that were copied. At the end of the script we display the value of the counter:
"Total Files: " + $y
No matter how many files have been copied, this counter will be empty. The reason for that is because this is the counter we’re incrementing:
$x = $x + 1
and this the counter we’re displaying:
$y
That’s right, we’re displaying the wrong variable. Change the last line in the script to this to display the correct count of files copied:
"Total Files: " + $x
That’s everything. Here’s the final, working script:
foreach ($i in Get-ChildItem C:\Scripts -recurse)
{
if (($i.CreationTime -lt ($(Get-Date).AddDays(-10))) -and ($i.Extension -eq ".txt"))
{
# Copy-Item $i.FullName C:\old
$i.Name
$x = $x + 1
}
}
""
"Total Files: " + $x