Deleting Old Folders Programmatically

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I need to delete some folders that exist under a root folder based upon how long they have been in existence. I do not need to delete the root folder, only the child folders. What is happening is that our logging routine creates new logs every hour, and creates a new log directory every day. These things are never analyzed, and we have no way to set the logging level. It is conceivable that we might one day need to look at these log files, but in the five years since I have been at the company, we have never had a problem with the application that was solved by looking at the log. On the other hand, we have had several problems that were caused by the logs—they eat up all the disk space. This stupid application is mission critical, and we cannot upgrade the application because the software company went out of business. I know why they went out of business—they wrote crappy software. Anyway, I had this idea that if we do not need to look at the log files for two days, we will never need to look at them, and therefore we need to delete the old child folders. Any ideas?

- AT

SpacerHey, Scripting Guy! Answer

Hi AT,

You know one thing that has always amazed me: It seems that every company in the entire world has at least one instance of Crapy App 1.0, and it is your mission-critical application. This is very easy to do in Windows PowerShell. As seen in the DeleteOldFolders.ps1 script, we create two variables: one for the path to the root folder, and one for the number of days old. We then use Get-ChildItem to recurse through the child folders under the test folder. Next, we pipeline the results to a Where-Object where we look at the CreationTime of the folder, use the –gt (greaterThan) operator, and add -2 days to our current date-time stamp. We then pipeline the results to the Remove-Item cmdlet.

Here is the DeleteOldFolders.ps1 script:

$rootFolder = "C:\test"
$daysOld = -2
Get-ChildItem -Path $rootFOlder -Recurse | 
Where-Object { $_.CreationTime -gt $(get-date).addDays($daysOld) } | 
Remove-Item

Top of pageTop of page

Searching For and Returning a Specific String

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! Do you have a method of grabbing a specific string from text and returning only the string that was searched for? I have tried using Select-String and qgrep, but they both return more than just the text I was searching for.

- CS

SpacerHey, Scripting Guy! Answer

Hi CS,

Of course we can do that. Because you were using Select-String, obviously you are interested in Windows PowerShell. Suppose we create an array of strings and store the array in a variable called (innocuously enough) $string. Like this:

PS C:\> $string = "first line","second line","third line"

Now we use the Select-String cmdlet that you suggested in your e-mail message. If we specify the pattern of “first,” all strings are returned as seen here:

PS C:\> Select-String -InputObject $string -Pattern "first" 
first line second line third line

On the other hand, if we use the –match operator and tell it to match the pattern of “first,” we see the first line is returned. This is the first element in our array of strings. But it returns the entire line. This is an improvement, but does not match (pardon the pun) our exact requirements:

PS C:\> $string -match "first"
first line

What we will do is use the static method match from the System.Text.RegularExpressions.Regex class. This is such an important class in Windows PowerShell that we have a type accelerator, [regex], that we can use to avoid all the typing. The match method takes two parameters: the first is the string to search and the second is the pattern to look for. We are still using the “first” pattern. Here is what the code looks like:

PS C:\> $matches = [regex]::match($string,"first")

We stored the returning match object in the variable $matches. When we print out the contents of the object we stored in the $matches variable, we see several properties. The one we are interested in is the value property. This is seen here:

PS C:\> $Matches 
Groups   : {first}
Success  : True
Captures : {first}
Index    : 0
Length   : 5
Value    : first

To display only the value of the match object stored in the $matches variable, we use the dotted notation $Matches.Value. This works the same as in VBScript. Here is the value pattern we were looking for:

PS C:\> $Matches.Value
First

Top of pageTop of page

Troubleshooting a Script

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am having a problem with a script. I keep getting errors. Here is the script:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") 
Set processes = objWMIService.ExecQuery _ ("select * from Win32_Process")
For Each objProcess in processes 
       Set processtime = sobjWMIService.ExecQuery _ ("select * from 
          Win32_PerfRawData_PerfProc_Process where name='objProcess.Name' ")
        For Each objProcessTime in processtime
                  endCPUTime = objProcessTime.PercentProcessorTime
                  startCPUTime = objProcessTime.Timestamp_Sys100NS
               CPUTime=startCPUTime-endCPUTime
                Wscript.Echo "Process Name: " & objProcess.Name
                       Wscript.Echo "Process CPU Usage: " & CPUTime
Next
Next
WScript.Quit ;

- PRSpacerHey, Scripting Guy! Answer

Hi PR,

Interesting script. I can see several reasons why the script will not work. First of all, when you copied the script from somewhere, you included the line continuation characters. In VBScript, the underscore character (_) means line continuation. It is only used or needed when a line is so long that it wraps to the next line. One particularly evil thing is that most books limit their code line lengths to 75–80 characters. However, most developers keep their monitors at a very high resolution that are easily capable of displaying 160 or more characters of code on a single line. When this code is printed, it introduces many line continuation characters. From years of teaching VBScript, I have found line continuation to be a particularly difficult task to master because of the complexity of quotations that are required to make the code work when the line is broken and continued to the next line. Here is an example of a line with the continuation character in it that is not needed—and that causes the line of code to fail:

Set processes = objWMIService.ExecQuery _ ("select * from Win32_Process")

To fix this line, we simply remove the line continuation character:

Set processes = objWMIService.ExecQuery("select * from Win32_Process")

This line of code…

Set processtime = sobjWMIService.ExecQuery _ 
           ("select * from Win32_PerfRawData_PerfProc_Process where name='objProcess.Name' ")

…actually has, I think, four separate errors in it:

Your object that connects into WMI (called an sWbemServices object) is named in your code objWMIService, not sobjWMIService. This is shown here: Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

You have a line continuation character after ExecQuery that is not needed.

You need to continue the line because you have wrapped your query.

You are trying to filter on the name property of the individual process, but it is included within the query itself.

Here is how the query should have been written:

("select * from Win32_PerfRawData_PerfProc_Process where name = " + objProcess.Name

The problem you see above is that when you put your variable inside the quotation marks, it was turned into a plain string. Outside the quotation marks, it is evaluated as an object, and we are able to retrieve the name property. (Interestingly enough, the expanding string feature of Windows PowerShell solves this kind of issue.)

Okay, enough of all that. If we make the above changes to the script, we might have a fighting chance of the script actually working. There is still a problem. This is the fact that—to be blunt—your approach is all wrong anyway. The way your script works is that you are using Win32_Process to obtain a list of all the processes on the computer. You are then using the RAW performance monitoring WMI class Win32_PerfRawData_PerfProc_Process to get information about each process that is running on the machine. You are cycling through the collection of processes you obtained in the first query, retrieving the name of each process, and using that for a second query. I admire your tenacity, and your work ethic. But dude! There has got to be a better way. And indeed there is. All you need to is to query Win32_PerfRawData_PerfProc_Process and retrieve the interesting properties. The reason for this is that Win32_PerfRawData_PerfProc_Process already knows about all the processes that are running on the computer. In this way, we can drastically cut down on the text of the script, and vastly remove chances for errors. Here is the revised script;

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set processes = objWMIService.ExecQuery("select * from Win32_PerfRawData_PerfProc_Process")
For Each objProcess In processes 
  endCPUTime = objProcess.PercentProcessorTime
  startCPUTime = objProcess.Timestamp_Sys100NS
  CPUTime=startCPUTime-endCPUTime
  Wscript.Echo "Process Name: " & objProcess.Name
  Wscript.Echo "Process CPU Usage: " & CPUTime
Next

Oh, one more thing. I deleted the Wscript.Quit command because when the script finishes running, it quits anyway. So unless you get paid by the number of lines of code you write, you do not really need to include that command.

That’s it for another Quick-Hits Friday. It is now the weekend (at least in my neck of the woods), and I am heading out to the wood shop to make saw dust. We’ll be on vacation next week and the week after, so enjoy some greatest hits from the Scripting Guys.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys