PSArchiveItemByDate.psm1

Set-StrictMode -Version Latest

$ErrorActionPreference = "Stop"
$Script:ArchiveDirName = "Archive"

class ArchiveItemByDateException : Exception {
    ArchiveItemByDateException([String]$Message) : base([String]$Message) {}
}

class ArchiveDirNotFoundException : ArchiveItemByDateException {
    ArchiveDirNotFoundException([String]$Message) : base([String]$Message) {}
}

class RootDirCannotArchiveException : ArchiveItemByDateException {
    RootDirCannotArchiveException([String]$Message) : base([String]$Message) {}
}

class InvalidDateException : ArchiveItemByDateException {
    InvalidDateException([String]$Message) : base([String]$Message) {}
}

class SourcePath {
    [String]$Path
    SourcePath($Path) {
        $this.Path = $Path
    }
    hidden [String]GetArchiveDirectoryInPath() {
        <#
            .SYNOPSIS
            Get the Archive directory from the path.
        #>

        $Local:Path = $this.Path
        while ($Local:Path.Length -gt 0) {
            $Name = Split-Path $Local:Path -Leaf
            if ($Name -ceq $Script:ArchiveDirName) {
                return $Local:Path
            }
            $Local:Path = Split-Path $Local:Path -Parent
        }
        throw [ArchiveDirNotFoundException]::new('Archive directory not found in path.')

    }
    hidden [bool]IsArchived() {
        <#
            .SYNOPSIS
            If the path is Archived, return True.
        #>

        try {
            $this.GetArchiveDirectoryInPath()
            return $true
        } catch [ArchiveDirNotFoundException] {
            return $false
        }
    }
    hidden [String]GetArchiveDirectory() {
        <#
            .SYNOPSIS
            Get the Archive directory for that path.
        #>

        if ($this.IsArchived()) {
            return $this.GetArchiveDirectoryInPath()
        } else {
            $ParentDir = Split-Path $this.Path -Parent
            if ($ParentDir.Length -eq 0) {
                throw [RootDirCannotArchiveException]
            }
            return Join-Path $ParentDir $Script:ArchiveDirName
        }
    }
    hidden [System.Object]GetDateFromBeginningOfName() {
        <#
            .SYNOPSIS
            Get Date from the beginning of the file name.
        #>

        $Name = Split-Path $this.Path -Leaf
        if ($Name.Length -eq $this.Path.Length) {
            throw [RootDirCannotArchiveException]::new('Root directory cannot archive.')
        }
        if ($Name -match "^(?<Year>\d{4})(?<Month>\d{2})(?<Day>\d{2})\D") {
            try {
                return Get-Date -Year $Matches.Year -Month $Matches.Month -Day $Matches.Day -Hour 0 -Minute 0 -Second 0 -Millisecond 0
            } catch {
                throw [InvalidDateException]::new("Invalid date found.")
            }
        }
        throw [InvalidDateException]"Date string not found."
    }
    [String]GetDestinationDirectory() {
        <#
            .SYNOPSIS
            Get the destination directory.
        #>

        $Now = Get-Date

        try {
            $Date = $this.GetDateFromBeginningOfName()
        } catch [InvalidDateException] {
            return ''
        }

        try {
            $ArchiveDir = $this.GetArchiveDirectory()
        } catch [RootDirCannotArchiveException] {
            return ''
        }
        if ($Date.Year -lt $Now.Year) {
            $ArchiveDir = Join-Path $ArchiveDir (Get-Date $Date -Format 'yyyy')
        }
        return Join-Path $ArchiveDir (Get-Date $Date -Format 'yyyyMM')
    }
}

function Move-ItemToArchiveDirectoryByDateInName {
    <#
        .SYNOPSIS
        Move the file to a directory for archiving.
 
        .DESCRIPTION
        If the first eight characters of a file's name express a date,
        move the file to a directory for archiving.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [String[]]
        [Parameter(Mandatory,ValueFromRemainingArguments)]
        $Path
    )
    $Path |
    ForEach-Object {
        $AbsPath = Resolve-Path $_
        $SourcePath = [SourcePath]::new($AbsPath)
        $DestDir = $SourcePath.GetDestinationDirectory()
        if ($DestDir -ne '') {
            New-Item -ItemType Directory -Force $DestDir
            Move-Item -Path $AbsPath -Destination $DestDir
        }
    }
}

Export-ModuleMember -Function Move-ItemToArchiveDirectoryByDateInName