PSStow.psm1

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop';

Function CreateResult($Success, $Message, $Item, $Store) {
    return [pscustomobject]@{
        'Success' = $Success;
        'Message' = $Message;
        'Item' = $Item;
        'Store' = $Store
    };
}

Function GetRoot([string]$Path) {
    return [System.IO.Path]::GetPathRoot($Path);
}

Function GetAbsolutePath {
    param([string]$Path)
    
    [IO.Path]::GetFullPath([IO.Path]::Combine((Get-Location).ProviderPath, $Path));
}

Function RemoveRoot([string]$Path) {
    $root = GetRoot $Path
    $Path.Replace($root, '');
}

Function ValidatePathStoreRelationship {
    param([string]$Path, [string]$Store)

    $Path = GetAbsolutePath $Path;
    $Store = GetAbsolutePath $Store;

    # for now, then change it to allow different roots.
    if ((GetRoot $Path) -ne (GetRoot $Store)) {
        return 'Item Path and Store must in the same root';
    }

    if ($Path.StartsWith($Store)) {
        return 'Item cannot be be a sub-directory of Store';
    }

    if ($Store.StartsWith($Path)) {
        return 'Item cannot be a parent directory of Store';
    }
}

Function GetAbsoluteStorePath {
    param([string]$Path, $Store) 

    # e.g. given a store C:\Dirs\MyStore and path to safeStore like C:\Users\th203\hello\world, you get
    # C:\Dirs\MyStore\Users\th203\hello\world
    return [IO.Path]::Combine((GetAbsolutePath $Store), (RemoveRoot (GetAbsolutePath $Path)));
}

Function Stow-Item {
    param([Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)][string[]]$Path, [Parameter(Mandatory=$true)][string]$Store)
    
    process {
        foreach ($Item in $Path) {
            
            $createResult = { 
                param($Success, $Message)
                return CreateResult -Success $Success -Item $Item -Message $Message -Store $Store
            };

            $validationResult = ValidatePathStoreRelationship -Path $Item -Store $Store;

            if ($validationResult) {
                return &$createResult $false $validationResult
            }

            if (-not (Test-Path $Item)) {
                return &$createResult $null 'Item does not exist'
            }
            
            try {
                $fullStorePath = GetAbsoluteStorePath $Item $Store;

                if (Test-Path $fullStorePath) {
                    return &$createResult $false "There is already a folder $fullStorePath"  
                }
                
                # given a fullpath of C:\Dirs\MyStore\Users\th203\hello\world, you get
                # C:\Dirs\MyStore\Users\th203\hello\
                $storeParent = Split-Path $fullStorePath;
                if (-not (Test-Path $storeParent)) {
                    # create the parent directory if it does not exist
                    mkdir $storeParent -ErrorAction Stop | Out-Null
                }
                
                Move-Item $Item $storeParent -ErrorAction Stop
                return &$createResult $true "Successfully moved $Item to $storeParent"
                
            } catch {
                return &$createResult $false $_.Exception.Message  
            }
        }
    }
}

Function Unstow-Item {
    param([Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)][string[]]$Path, [Parameter(Mandatory=$true)][string]$Store)

    process {
        foreach ($Item in $Path) {

            $createResult = { 
                param($Success, $Message)
                return CreateResult -Success $Success -Item $Item -Message $Message -Store $Store
            };

            # given $store C:\_data\Store
            # and a $Item C:\mydata\myjavadata
            # $storeLocation will be C:\_data\Store\mydata\myjavadata
            $storeLocation = GetAbsoluteStorePath $Item $Store;

            $validationResult = ValidatePathStoreRelationship -Path $Item -Store $Store;

            if ($validationResult) {
                return &$createResult $false $validationResult;
            }

            if (-not (Test-Path $storeLocation)) {
                return &$createResult $null "Store location does not exist"  
            }
            
            if (Test-Path $Item) {
                return &$createResult $false "Destination exists: $( GetAbsolutePath $Item )"  
            }
            
            try {
                $parentPath = Split-Path $Item;

                if (-not (Test-Path $parentPath)) {
                    mkdir $parentPath -ErrorAction Stop | Out-Null
                }

                Move-Item $storeLocation $parentPath -ErrorAction Stop;
                return &$createResult $true "Successfully returned to $Item"  
            } catch {
                return &$createResult $false $_.Exception.Message  
            }
        }
    }
}

Export-ModuleMember -Function Stow-Item
Export-ModuleMember -Function Unstow-Item