lib/Junction.ps1

function Get-JunctionTarget {
    <#
    .SYNOPSIS
        Returns the target path of an existing junction or symlink, working
        on both Windows PowerShell 5.1 (uses .Target) and PowerShell 7+
        (uses .LinkTarget). Returns $null when no target can be determined.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $Path
    )

    $item = Get-Item -Path $Path -Force -ErrorAction Stop

    foreach ($prop in 'LinkTarget', 'Target') {
        if ($item.PSObject.Properties[$prop]) {
            $value = $item.$prop
            if ($value) {
                if ($value -is [array]) { return [string]$value[0] }
                return [string]$value
            }
        }
    }

    return $null
}

function New-DevJunction {
    <#
    .SYNOPSIS
        Migrates a directory's contents to a target path and replaces the
        original location with an NTFS junction. Verifies that an existing
        junction points to the expected target before skipping.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory)]
        [string] $LinkPath,

        [Parameter(Mandatory)]
        [string] $TargetPath,

        [Parameter(Mandatory)]
        [string] $Name
    )

    if (-not (Test-Path -Path $TargetPath -PathType Container)) {
        if ($PSCmdlet.ShouldProcess($TargetPath, 'Create directory')) {
            New-Item -ItemType Directory -Path $TargetPath -Force | Out-Null
        }
    }

    if (Test-Path -Path $LinkPath) {
        $item = Get-Item -Path $LinkPath -Force
        if ($item.Attributes -match 'ReparsePoint') {
            $existingTarget = Get-JunctionTarget -Path $LinkPath
            $expected = $TargetPath.TrimEnd('\', '/')
            $actual   = if ($existingTarget) { $existingTarget.TrimEnd('\', '/') } else { '<unknown>' }

            if ($actual -ieq $expected) {
                Write-Status -Level Info -Message " [INFO] $Name is already a junction to $TargetPath"
            }
            else {
                Write-Status -Level Warn -Message " [WARN] $Name is a junction to '$actual' (expected '$expected')."
                Write-Status -Level Warn -Message " Remove the existing junction manually or update `$Paths to match."
            }
            return
        }

        $children = @(Get-ChildItem -Path $LinkPath -Force)
        if ($children.Count -gt 0) {
            if ($PSCmdlet.ShouldProcess("$LinkPath -> $TargetPath", "Move $Name data")) {
                Write-Status -Level Info -Message " [INFO] Relocating $Name data to $TargetPath..."
                $children | Move-Item -Destination $TargetPath -Force
            }
        }

        if ($PSCmdlet.ShouldProcess($LinkPath, 'Remove (will be replaced with junction)')) {
            Remove-Item -Path $LinkPath -Recurse -Force
        }
    }
    else {
        Write-Status -Level Info -Message " [INFO] $LinkPath does not exist; creating junction directly."
    }

    if ($PSCmdlet.ShouldProcess("$LinkPath -> $TargetPath", 'Create junction')) {
        New-Item -ItemType Junction -Path $LinkPath -Value $TargetPath | Out-Null
        Write-Status -Level Info -Message " [INFO] Junction created: $LinkPath -> $TargetPath"
    }
}