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, '') -replace '^(/|\\)','';
}

Function CombinePath {
    [IO.Path]::Combine(([string[]]$args));
}

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 Get-AbsoluteStowStoragePath {
    param([string]$Path, $Store) 
   
    # if you have a store path like
    # C:\MyStore
    # and an item
    # C:\Users\th203\hello\world
    # you get
    # C:\MyStore\-C\Users\th203\hello\world
    # if there was a directory in the D:\ drive:
    # D:\Java\FurtherJava
    # you get
    # C:\MyStore\-D\Java\FurtherJava

    $absPath = GetAbsolutePath $Path;

    $rootDir = "";

    $absPathRoot = [IO.Path]::GetPathRoot($absPath)
    if ($absPathRoot -match '^[a-z]+:\\$') {
        $driveLetter = $absPathRoot -replace '^([a-z]+):\\$','$1';
        $rootDir = "-$driveLetter";
    } else {
        $rootDir = ($absPathRoot -replace '^(//|\\\\)','--') -replace '/','\'
    }

    CombinePath (GetAbsolutePath $Store) ($rootDir) (RemoveRoot $absPath);
}

Function Stow-Item {
    param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)][string[]]$Path, 
        [Parameter(Mandatory=$true,Position=1)][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 = Get-AbsoluteStowStoragePath $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
                    try {
                        mkdir $storeParent -ErrorAction Stop | Out-Null
                    } catch {
                        return &$createResult $false "Could not create directory '$storeParent': $($_.Exception.Message)" 
                    }
                }
                
                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,Position=1)][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 = Get-AbsoluteStowStoragePath $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  
            }
        }
    }
}