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
{
}
}
Like this:
Like Loading...
Leave a Reply