recursionpowershell-2.0cab

Recursion Logic


I've been working on a PowerShell script that will preserve directory structure when cabbing nested folders. I'm having a bit of difficulty getting the recursion logic right, though.

This is a rough, so there isn't any try/catch error code yet. I've commented out the Remove-Item to prevent accidental runs.

The logic I worked out for this is as follows.

function Chkfordir ($clevel)
{
    $dir = dir $clevel | ? { $_.PSIsContainer -eq $true } #Does Current Level have Folders?
    if($dir -ne $null)    # Yes
    {
        Chkfordir $dir        #Go Deeper
    }
    if ($dir -eq $null)   #Deepest Branch
    {
        return                # Go Back One Level and begin Cabbing
    }

    $dir | % { 
        Compress-Directory $_.FullName (".\" + [string]$_.Name + ".cab")
        echo ($_.FullName + ".cab" >> .\cleaf.log"
        #Remove-Item -Recurse $_.FullName
        return
    } 
}

The function call Compress-Directory is from here.

Edit Changes:

Will Re-Post Code Soon (08/18)

Edit 08/18 So I finally had a chance to test it and the logic seems to work now. There were some problems.

Most of the difficulty came with a powershell gotcha and the unnoticed problem that Compress-Directory is not path independent. Looks like I'll be needing to re-write this function later to be path independent.

The powershell gotcha was in a type change for a value on the pipeline. Apparently after returning from a function directory items are changed from System.IO.FileInfo to System.IO.DirectoryInfo with differently named member functions.

Echo was replaced with Add-Content as the redirection operators don't work in powershell.

There were some unaccounted states to contend with as well. A leaf directory which had no files would cause Compress-Directory to error or complete silently with no file creation (thus not preserving hierarchy).

Solution was to add an Add-Content for leaf folders before return, and moved Add-Content to before the Compress-Directory so there is at least one file in each directory.

I've included my current version below but it is a work in progress.

function Chkfordir ($clevel)
{
    $dir = dir $clevel | ? { $_.PSIsContainer -eq $true } # Get Folders?
    if ($dir -eq $null) {  #Check if deepest branch
        Add-Content (Join-Path $_.PSPath "\leaf.log") ([string]$_.FullName + ".cab")
        return $_                # Return one level up and cab
    }

    $dir | % { #for each sub go deeper
        Chkfordir $_.FullName
        Add-Content (Join-Path $_.PSParentPath "\branch.log") ([string]$_.FullName + ".cab")
        Compress-Directory $_.FullName ([string]$_.Name + ".cab")
        #Remove-Item $_.FullName -recurse
    }          
}

Solution

  • You need to recurse for each subdirectory and compress it after the recursive call returns:

    function Chkfordir($clevel) {
        Get-ChildItem $clevel |
            Where-Object { $_.PSIsContainer } |
            ForEach-Object {
                Chkfordir $_
                Compress-Directory ...
                ...
            }
    }
    

    That way you automatically descend first, then create the archives as you return.