Powershell zip subfolders recursively, conditionally
[ValidateScript({
    if (Test-Path $_ -PathType Container) {$true}
    else {Throw "Folder $_ not found"}})]


Test-Path already returns a [bool] which is what is expected by [ValidateScript()] so this can be simplified by removing the if/else:

[ValidateScript(
    (Test-Path $_ -PathType Container) -or $(throw "Folder $_ not found")
)]




pwd is an alias for Get-Location. I always recommend avoiding aliases and abbreviations in reusable scripts.



if ($testzip -eq $True)


This can be simplified to:

if ($testzip)




Else{
"Zip dll not found or couldn't be loaded. 
Check file is present and unblocked. 
Exiting" &gt;&gt; "$WorkingDirectory\OutputLog.txt" 
Exit
}


This should probably be thrown as an exception:

else {
    $e = "Zip dll not found or couldn't be loaded. 
    Check file is present and unblocked. 
    Exiting" 
    $e &gt;&gt; "$WorkingDirectory\OutputLog.txt" 
    throw [System.IO.FileNotFoundException]$e
}




$path = $s  #$s variable contains each folder
$path 
Set-Location $path.FullName


There are a two things I want to address here.

First, the middle line:

$path


This is implicitly calling Write-Object $path which in your script is probably writing this out to the console, and that's probably the desired behavior. You should be aware that this actually makes it the return value of the current scope/scriptblock you're in, which means it's part of the return value of your script.

You might not care about that right now, and it might never matter because you'll never call this script from something that checks its output, but it's bad practice (unless you very explicitly do want that to be part or all of its return value).

If all you want is to show the path on the screen, use Write-Host. Or even better, add [CmdletBinding()] above your param() block and then use Write-Verbose. That will only show the output when someone calls the script with -Verbose.

Second, I believe you should avoid changing the current path when possible. If it's not possible or it's impractical, then at least make use of Push-Location and Pop-Location along with try/finally so that you restore the original working directory.



if ( $(Try { Test-Path $items } 
    Catch { "Cannot find items in $fullpath. 
    Sub-folders will be processed afterwards. 
    ERROR: $_" &gt;&gt;  "$WorkingDirectory\OutputLog.txt"  }) ) {


This is confusing. Test-Path should not throw an exception; it should return $false when the path doesn't exist, so your catch should never run. In addition, if it did run, the entire expression might still return $true making the if always satisfied.

Seems this would be better written as:

if ( Test-Path $items ) {
    # ... do stuff
} else {
    "Cannot find items in $fullpath. 
    Sub-folders will be processed afterwards." &gt;&gt;  "$WorkingDirectory\OutputLog.txt"
}




You should wrap your logging into a function instead of having "Some message"  &gt;&gt;  "$WorkingDirectory\OutputLog.txt" strewn about the code. If you ever have to When you have to update that it will be a pain.

Consider something like this:

function Write-Log {
[CmdletBinding()]
param(
    [Parameter(
        Mandatory=$true
    )]
    [ValidateNotNullOrEmpty()]
    [String]
    $Message ,

    [Parameter(
        Mandatory=$true
    )]
    [ValidateNotNullOrEmpty()]
    [ValidateScript( { $_ | Test-Path -PathType Leaf -IsValid } )]
    [String]
    $LogPath
)
    $Message  &gt;&gt;  $LogPath
}


Now you can do:

Write-Log -Message "Compressing $src to $dest" -LogPath "$WorkingDirectory\OutputLog.txt"


And, you can do this at the top of the script (with v3 or higher):

$PSDefaultParameterValues = @{
    "Write-Log:LogPath" = "$WorkingDirectory\OutputLog.txt"
}


And then just do:

Write-Log -Message "Compressing $src to $dest"


Of course you could also make -LogPath optional, and give it a default value if you must (for v2 and below compatibility).

Even if you have to supply the log file every time though, so what, you're already doing that. And wrapping this in a function gives you more options for changing how its written later (like automatically adding a timestamp to each log entry).