Public/Convert-FSMSharePath.ps1

function Convert-FSMSharePath {
    <#
    .SYNOPSIS
        Produces a new share-inventory CSV with paths remapped from an old root to a new root.

    .DESCRIPTION
        Reads an exported share CSV and rewrites the start of each Path that begins with
        -OldRoot so it begins with -NewRoot instead. For example D:\Shares\HR becomes
        E:\Data\HR when OldRoot is 'D:\Shares' and NewRoot is 'E:\Data'.

        Matching is case-insensitive (correct for Windows paths). Any path that does NOT
        start with OldRoot is left unchanged and reported as a warning, so you can spot
        shares that would otherwise be recreated on the wrong drive.

    .PARAMETER InputPath
        The source share CSV (from Export-FSMShareInventory).

    .PARAMETER OutputPath
        Where to write the remapped CSV.

    .PARAMETER OldRoot
        The path prefix to replace, e.g. 'D:\Shares'.

    .PARAMETER NewRoot
        The replacement prefix, e.g. 'E:\Data'.

    .EXAMPLE
        Convert-FSMSharePath -InputPath C:\Temp\shares.csv -OutputPath C:\Temp\remapped.csv -OldRoot 'D:\Shares' -NewRoot 'E:\Data'
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '',
        Justification = 'The MatchEvaluator delegate signature requires a match parameter; replacement is a constant new root, so the match itself is intentionally unused.')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$InputPath,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$OutputPath,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$OldRoot,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$NewRoot
    )

    if (-not (Test-Path -Path $InputPath)) {
        throw "Input CSV not found: '$InputPath'."
    }

    $shares = @(Import-Csv -Path $InputPath)
    Assert-FSMCsvColumn -InputObject $shares -RequiredColumn 'Path' -Path $InputPath

    $escapedOldRoot = [regex]::Escape($OldRoot.TrimEnd('\'))
    $newRootClean   = $NewRoot.TrimEnd('\')
    $pattern        = "^$escapedOldRoot"
    $unmatched      = 0

    foreach ($share in $shares) {
        $original = "$($share.Path)"
        # Delegate replacement avoids any $-substitution issues if NewRoot contains '$'.
        $updated = [regex]::Replace(
            $original,
            $pattern,
            { param($m) $newRootClean },
            [Text.RegularExpressions.RegexOptions]::IgnoreCase
        )

        if ($updated -eq $original) {
            $unmatched++
            $name = if ($share.PSObject.Properties.Name -contains 'Name') { $share.Name } else { '<unknown>' }
            Write-FSMStatus -Message "Path not remapped (does not start with '$OldRoot'): share '$name' -> $original" -Level Warning
        }

        $share.Path = $updated
    }

    $folder = Split-Path -Path $OutputPath -Parent
    if ($folder -and -not (Test-Path -Path $folder)) {
        New-Item -Path $folder -ItemType Directory -Force | Out-Null
    }

    $shares | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8

    $remapped = $shares.Count - $unmatched
    Write-FSMStatus -Message "Remapped $remapped of $($shares.Count) path(s) -> $OutputPath ($unmatched left unchanged)" -Level Success
    return $shares
}