Powershell Archive Script

function Move-Archive
{
    <#
        .SYNOPSIS
        Selects folders from a source folder to archive to a new location.  A symbolic link
        is left behind pointing to the new location.
        .DESCRIPTION
        This command copies the source folders, removes them and creates a new symbolic link
        with MKLINK /J.  It checks the source and target folders are the same size before
        proceeding with the deletion.
        .PARAMETER SourcePath
        The parent folder with folders to be archived.
        .PARAMETER ArchivePath
        The parent folder where folders will be archived to.
        .PARAMETER Folder
        An explicit folder or folders to be archived or retrieved from the archive.  Can take partial names and all
        folders matching the characters are returned or can be omitted to list all the folders in the
        SourcePath folder.
        .EXAMPLE
        Move-Archive -SourcePath "C:\Steam" -AchivePath "D:\Archive\SteamGames" -Folder "Quake"
        This example lists all the folders in C:\Steam which contains the name "Quake" and allows
        them to be archived.  It will also list all the previously archived folders containing
        that name and allow them to be restored.
        .EXAMPLE
         Move-Archive
        This example lists all the folders in the default Source path and all previously archived folders
        in the default Archive path and allows them to be archived or restored from the archive.
    #>
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline=$true)]
        [string[]]$Folder="",
        [ValidateScript(
        {
            if(!(Test-Path $_ -PathType Container))
            {
                Throw "SourcePath must be a valid path to a folder."
            }else
            {
                $True
            }
        }
        )]
        [string]$SourcePath="C:\Source",
        [ValidateScript(
        {
            if(!(Test-Path $_ -PathType Container))
            {
                Throw "ArchivePath must be a valid path to a folder."
            }else
            {
                $True
            }
        }
        )]
        [string]$ArchivePath="E:\Target"

    )
    BEGIN
    {
        function Get-FolderSize
        {
            param
            (
                [Parameter(Mandatory=$True)]
                [ValidateScript(
                    {
                        if(!(Test-Path -Path $_))
                        {
                            Throw ("Source path ($_) does not exist.")

                        }else
                        {
                            $True
                        }
                    }
                )]
                [string]$Path
            )
            #Recurse through subfolders in the path and add the size of any child items that aren't folders.
            Write-Debug "Entering Get-FolderSize function"
            Write-Debug "Parameter : Path = $Path"
            [int64]$Size=0;
            foreach ($ChildItem in Get-ChildItem -Path $Path)
            {
                if ($ChildItem.PSIsContainer)
                {
                    $Size=$Size+(Get-FolderSize -Path $ChildItem.FullName)
                }else
                {
                    $Size=$Size+$ChildItem.Length
                }
            }
            Write-Debug "Leaving Get-FolderSize function"
            return $Size
        }
        function Get-FolderInformation
        {
            param
            (
                [Parameter(Mandatory=$True)]
                    [string]$SourcePath,
                [Parameter(Mandatory=$True)]
                    [string]$ArchivePath,
                [Parameter(ValueFromPipeline=$true)]
                    [string]$Path
            )
            Begin
            {
                Write-Debug "Entering Get-FolderInformation function"
                Write-Debug "Parameter : SourcePath = $SourcePath"
                Write-Debug "Parameter : ArchivePath = $ArchivePath"
            }
            Process
            {

                Write-Debug "Parameter : Path = $Path"
                if (($Path -eq $Null) -or ($Path -eq ""))
                {
                    #If there is no Path specified, get everything from the Source folder.
                    $WorkingPath=$SourcePath+"\*"
                }elseif (Test-Path -Path $Path)
                {
                    #If Path is a valid full path, then use that
                    $WorkingPath=$Path
                }else
                {
                    #Otherwise assume it's for a wildcard search and build a search string from it and the SourcePath.
                    $WorkingPath=$SourcePath+"\*"+$Path+"*"
                }
                Write-Debug "WorkingPath = $WorkingPath"
                $WorkingFolderList=Get-Item $WorkingPath | Where-Object -FilterScript {$_.PSISContainer}
                ForEach ($ChildItem in $WorkingFolderList)
                {
                    Write-Debug $ChildItem.FullName
                    #Build a hash table with all the properties we're interested in.
                    $Properties=@{
                        'Name'=$ChildItem.Name;
                        'FullSourcePath'=$ChildItem.FullName;
                        'FullArchivePath'=$ArchivePath+"\"+$ChildItem.Name
                        'Size'=0;
                        'Direction'="To Archive";
                        'Moving'=$False
                        'Message'="";
                        'Successful'=$False
                    }
                    #Test if it's a folder or already a symbolic link; use this to determine if we're going to or from the archive.
                    if ($ChildItem.Attributes -like "*ReparsePoint*")
                    {
                        $Properties.Size=Get-FolderSize -Path $Properties.FullArchivePath
                        #If it's a symbolic link see if it's target is in the archive folder;  if it's going somewhere else
                        #we ignore it.
                        if (!(Test-Path $Properties.FullArchivePath))
                        {
                            return
                        }
                        $Properties.Direction="From Archive"
                    }else
                    {
                        $Properties.Size=Get-FolderSize -Path $ChildItem.FullName
                    }
                    #Build and output a PSObject from the hash table
                    $Object=New-Object -TypeName PSOBject -Property $Properties
                    Write-Output $Object
                }
            }
        }

        function Move-ArchiveObject
        {
            param
            (

                [Parameter(Mandatory=$True,ValueFromPipeline=$true)]
                    [PSObject]$SourceObject
            )
            BEGIN
            {

            }
            PROCESS
            {
                Write-Debug "Entering Move-ArchiveObject function"
                if ($SourceObject.Direction -eq "To Archive")
                {
                    #See if there's already a folder at the target location.
                    if(!(Test-Path -Path $SourceObject.FullArchivePath))
                    {
                        Copy-Item -Path $SourceObject.FullSourcePath -Destination $SourceObject.FullArchivePath -Recurse
                        #After copying the folder, make sure its size matches the original before deleting the original and
                        #creating the symbolic link.
                        if ($SourceObject.Size -eq (Get-FolderSize $SourceObject.FullArchivePath))
                        {
                            Remove-Item -Path $SourceObject.FullSourcePath -Recurse
                            & CMD.EXE /c "MKLINK /j $($SourceObject.FullSourcePath) $($SourceObject.FullArchivePath)" > $Null
                            $SourceObject.Successful=$True
                        }else
                        {
                            $SourceObject.Message="Source and Destination folder sizes differ.  Aborting archive operation."
                        }
                    }else
                    {
                        $SourceObject.Message="Destination already exists."
                    }
                }else
                {
                    #Delete the symbolic link and copy the original back to the sourcepath.
                    (Get-Item -Path $SourceObject.FullSourcePath).Delete()
                    Copy-Item -Path $SourceObject.FullArchivePath -Destination $SourceObject.FullSourcePath -Recurse
                    #Don't delete the archive unless the source copy's size matches the archive copy.
                    if ($SourceObject.Size -eq (Get-FolderSize -Path $SourceObject.FullSourcePath))
                    {
                        Remove-Item -Path $SourceObject.FullArchivePath -Recurse
                        $SourceObject.Successful=$True
                    }else
                    {
                        $SourceObject.Message="Source and Destination folder sizes differ.  Aborting archive operation."
                    }
                }
                Write-Output $SourceObject
                Write-Debug "Leaving Move-ArchiveObject function"
            }
        }

        function Format-FolderList
        {
            param
            (
                [Parameter(Mandatory=$True,ValueFromPipeline=$true)]
                    [psobject[]]$FolderList
            )
            BEGIN
            {
                $i=0;
            }
            PROCESS
            {
                #Add an Index attribute to show the order in the list.
                Write-Debug "Entering Get-FolderInformation function"
                foreach($Folder in $FolderList)
                {
                    $i++;
                    Add-Member -InputObject $Folder -MemberType NoteProperty -Name Index -Value $i
                    Write-Output $Folder
                }
                Write-Debug "Leaving Get-FolderInformation function"
            }
        }

        function Display-BytesAsKB
        {
            param
            (
                [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
                    $ByteCount
            )
            Return ("{0:N2} KB" -f ($ByteCount / 1KB))
        }
    }

    PROCESS
    {
        Write-Debug "Entering Move-Archive function"
        $Command=$Null
        #Build a sort a list of all the folders we're interested in.
        $FolderList=$Folder | Get-FolderInformation -SourcePath $SourcePath -ArchivePath $ArchivePath | Sort-Object -Property Size -Descending | Format-FolderList

        While (($Command -ne "") -and ($Command -ne "Exit"))
        {
            #List all the folders with an index number and allow the user to toggle them on or off for movement to/from the archive.
            $FolderList | Select-Object -Property Index ,Moving, Direction,FullSourcePath,FullArchivePath, @{name="FolderSize";expression={(Display-BytesAsKB -ByteCount $_.Size)}} | Format-Table -AutoSize
            $Command=Read-Host -Prompt "Hit [Return] to Continue, Type 'Exit' to Cancel or enter the Index number to toggle processing"
            if ([bool]($Command -as [int]))
            {
                ($FolderList[[int]$Command-1]).Moving=!(($FolderList[[int]$Command-1]).Moving)
            }
        }
        if ($Command -eq "")
        {
            $FolderList | Where-Object -FilterScript {$_.Moving}| Move-ArchiveObject | Format-Table -Property FullSourcePath,FullArchivePath,Successful,Message -AutoSize
        }
    }
    END
    {
    }
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s