Path Location
I see that you're trying to save the current location and then restore it later.
There are actually built-in cmdlets for this, Push-Location
and Pop-Location
. They work the same way as pushd
and popd
if you're familiar with those from the regular Windows command prompt for from unix-like systems (and in fact those terms are aliases in PowerShell and still work).
So you can actually just do this:
Push-Location # saves the current location
# do some stuff
Pop-Location # restores the original location
You can also give a location to change to, so for example instead of doing Set-Location
later, you can do something like this:
$dirToChangeTo = 'C:\Windows'
Push-Location $dirToChangeTo
# do stuff in the new location
Pop-Location # original location restored
Handling Errors
There's a small problem with any of these methods though: if your script doesn't execute all the way through for any reason, your original location will not be restored. For that you can wrap your entire script in a try
/finally
block:
Push-Location
try {
# all your code here
} finally {
Pop-Location
}
The idea with this is that the code in finally {}
should execute always (if a fatal exception occurs, if you stop the script, press CTRL C, etc.), so you can be fairly certain that your original location will get restored. I do this all the time when dealing with the SQLPS
module because it always changes the location to the SQLPS
drive.
Avoiding this altogether
Of course you really don't have to change the current location. The only place you're using it is where you're finding the zip files, but you could have just passed the current directory into the -Path
or -LiteralPath
parameter of Get-ChildItem
and then you would avoid the need to worry about changing it. I tend to prefer this method personally.
Iterating Directories
The Pipeline
foreach($path in get-childitem -recurse) { #get all subfolders
Get-ChildItem
is a pipeline cmdlet, so I would typically recommend you stay with the pipeline, as it tends to be a bit more natural in PowerShell:
Get-ChildItem -Recurse | ForEach-Object {
$path = $_
}
Getting Directories Only
if ($path.Attributes -eq "Directory") {
You don't need to test for a directory after the call. If you're using PowerShell 3 or higher, you can just do:
Get-ChildItem -Directory -Recurse | ForEach-Object {
$path = $_ # this will be a directory
}
If you're using v2 you can still keep it in the pipeline but it's a bit more verbose:
Get-ChildItem -Recurse | Where-Object { $_.PSIsContainer } | ForEach-Object {
$path = $_ # this will be a directory
}
Date Formatting
$date = "$((Get-Date).ToString('yyyy-MM-dd_hh-mm'))"
There's no need to embed this in a string; calling .ToString()
will already give you one!
Get-Date
has a -Format
parameter that takes the same format string as .ToString()
so you could just do:
$date = Get-Date -Format 'yyyy-MM-dd_hh-mm'
If you prefer .net objects, you could do it without any cmdlets:
$date = [DateTime]::Now.ToString('yyyy-MM-dd_hh-mm')
12 vs 24 hour clock?
This may be culture dependent and I'm not sure where you are, but on my system 'hh-mm'
will give me the 12-hour time, and you're not including AM/PM in your string.
So right now for me it's 8:14 PM and Get-Date -Format 'hh-mm'
gives me 08-14
. It would give me the same thing in 12 hours though. 'HH-mm'
will give me 20-14
which is probably what you want to do here instead of embedding am/pm, but if you did I believe the format code is tt
:
Get-Date -Format 'hh-mm tt'
Path Combining
$newpath = [io.path]::combine("$path$date","")
I'm not sure what you're doing here. This is the same thing as:
$newpath = "$path$date"
[System.IO.Path]::Combine()
is a way to combine multiple path components without having to concatenate them yourself (and deal with leading/trailing path separators). Am empty string as one of the parameters effectively gets ignored.
It's a great function to know though. There is also the Join-Path
cmdlet which does the same thing but in a more "PowerShell" style of course.
Aliases and Item Type
$items = Get-ChildItem -Exclude *.zip | ? {$_.LastWriteTime -lt (Get-date).AddDays(-31) -and -not $_.psIsContainer}
Aliases
?
is an alias for Where-Object
. Try to avoid aliases in scripts you intend to reuse, especially if they'll be shared or read by others.
Item Type
As above, if you're using PS3+ you can use Get-ChildItem -File
to get only files, otherwise you're already doing the right thing with .PSIsContainer
.
Test-Path
already returns [bool]
if ( $(Try { Test-Path $items } Catch { $false }) ) {
There's no need to use try
/catch
here; Test-Path
already returns $true
or $false
so you can just use it directly:
if (Test-Path $items) {
else { continue }
is Unnecessary
That's what was going to happen anyway!
Adding Parameters
I definitely get the idea of this operating on the current directory, but it would be nice to be able to launch it from anywhere and tell it what directory to operate on. To do that, you add a param()
block at the top of the script:
# very top of script, pretend this isn't here
[CmdletBinding()]
param(
[ValidateScript( { $_ | Test-Path -Type Container } )]
[String]
$InitPath = (Get-Location)
)
# rest of code
Now you can call your script with -InitPath C:\some\directory
or just leave it blank and it will use the current working directory anyway. And I threw in a parameter validation attribute to make sure that the passed in value exists and is actually a directory.
Parameterizing a script is something I tend to recommend a lot; it's something good to think about in general because it tends to make your code more versatile, testable, etc.