link_Nto1.ps1

<#PSScriptInfo
 
.VERSION 1.0.0
 
.GUID aca602e8-0ebe-43e8-84a7-ea1d33a3a4d0
 
.AUTHOR Vasil Kalchev
 
.COMPANYNAME
 
.COPYRIGHT MIT
 
.TAGS hard link symbolic link
 
.LICENSEURI https://github.com/VaSe7u/link_Nto1/blob/master/LICENSE
 
.PROJECTURI https://github.com/VaSe7u/link_Nto1
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
#>


<#
.SYNOPSIS
Link files from multiple directories into one.
.DESCRIPTION
Creates hard or symbolic links inside the destination directory to every file
from the source directories.
 
This can be useful when you want to specify multiple folders to be consumed by
a program, but the program only allows selecting a single folder and it doesn't
iterate through its sub-directories (e.g. "Windows 10 Desktop Background
Slideshow"). This way you are able to quickly reconfigure the set of folders
that will be consumed.
 
How to create a parameterized, runnable shortcut?
Create the shortcut. In `Properties`, field `Target`: prepend "powershell"
before "\path\link_Nto1.ps1" and after it, append the desired arguments. Set
field `Start in` according to the location of the specified folders.
 
.NOTES
Files with equal names are considered the same.
 
.PARAMETER dst
Destination directory.
.PARAMETER srcs
One or more source directories.
.PARAMETER ItemType
Either 'HardLink' or 'SymbolicLink'. Default: 'HardLink'
 
.EXAMPLE
PS> .\link_Nto1.ps1 dst_dir src_dir1,src_dir2
.EXAMPLE
PS> .\link_Nto1.ps1 -dst dst_dir -srcs src_dir1 -ItemType SymbolicLink
 
.LINK
https://github.com/VaSe7u/link_Nto1
#>



param(
    [parameter(mandatory)] [string] $dst,
    [parameter(mandatory)] [string[]] $srcs,
    [validateset('HardLink','SymbolicLink')] [string] $ItemType='HardLink'
)

$dst_files = @()  # array that will be populated with all destination file names
$src_files = @()  # array that will be populated with all source file names

# populate the `dst_files` array
Write-Verbose("Populating ``dst_files``:")
Get-ChildItem ".\$dst" |
ForEach-Object {
    $dst_files += $_.ToString()
    Write-Verbose(" - $_")
}

# determine the linking verb string
$verb = ""
if ($ItemType -eq 'SymbolicLink') {
    $verb = "soft"
} elseif ($ItemType -eq 'HardLink') {
    $verb = "hard"
} else {
    # should never happen, since `ItemType` parameter is validated
    Write-Error("unknown ``ItemType``, terminating")
    Exit(1)
}


$srcs_cntr = 0
foreach($src in $srcs) {  # iterate through the specified source directories

    # srcs progress bar ---
    $srcs_cntr++
    $srcs_progress_activity = "Iterating directory"
    $status = "$srcs_cntr/$($srcs.count)"
    $percent = (($srcs_cntr / $($srcs.count)) * 100)
    Write-Progress -Id 0 -Activity $srcs_progress_activity -Status $status -CurrentOperation $src -PercentComplete $percent
    # Start-Sleep -Seconds 3
    # srcs progress bar ~~~

    Write-Output("Iterating directory .\$src :")

    $file_cntr = 0
    Get-ChildItem ".\$src" |
    Foreach-Object {

        # files progress bar ---
        $file_cntr++
        $files_progress_activity = ($verb.toCharArray()[0].tostring().toUpper() + $verb.remove(0, 1)) + " linking"
        $status = "$file_cntr/$((Get-ChildItem ".\$src").count)"
        $percent = (($file_cntr / $((Get-ChildItem ".\$src").count)) * 100)
        Write-Progress -Id 1 -ParentId 0 -Activity $files_progress_activity -Status $status -CurrentOperation $_ -PercentComplete $percent
        # files progress bar ~~~

        Write-Host(" - $verb linking .\$src\$_ to .\$dst\$_") -NoNewline

        # linking will fail if the file already exists
        try {
            New-Item -ItemType $ItemType -ErrorAction Stop -Path ".\$dst\$_" -Target ".\$src\$_" > $null
            Write-Output(" - success")
        } catch [System.IO.IOException] {
            Write-Output(" - file already exists")
        }

        $src_files += $_.ToString()  # remember every file that was linked

    }
}

# get rid of the progress bars
Write-Progress -Id 0 -Activity $srcs_progress_activity -Status "Ready" -Completed
Write-Progress -Id 1 -ParentId 0 -Activity $files_progress_activity -Status "Ready" -Completed


Write-Verbose("Deleting missing files from destination:")
$file_cntr = 0
foreach($file in $dst_files) {

    # del progress bar ---
    $file_cntr++
    $deleting_progress_activity = "Deleting missing files from destination"
    $status = "$file_cntr/$($dst_files.count)"
    $percent = (($file_cntr / $($dst_files.count)) * 100)
    Write-Progress -Id 2 -Activity $deleting_progress_activity -Status $status -CurrentOperation $file -PercentComplete $percent
    # del progress bar ~~~

    if ($file -notin $src_files) {
        Write-Verbose " - $file"
        Remove-Item .\$dst\$file
    }
}