Netscoot.Core/Private/MSBuildImports.ps1

function Find-MSBuildFiles {
    # Project files + shared .props/.targets beneath a root (anything that can <Import>).
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Root)
    $exts = @('.csproj', '.fsproj', '.vbproj', '.vcxproj', '.props', '.targets')
    Get-ChildItem -LiteralPath $Root -Recurse -File -ErrorAction SilentlyContinue |
        Where-Object { $_.Extension -in $exts -and $_.FullName -notmatch '[\\/](bin|obj|\.vs|\.git)[\\/]' }
}

function Get-ImportPaths {
    # <Import Project="X"> entries in an MSBuild file. Resolves literal relative paths and the
    # $(MSBuildThisFileDirectory) token (= the file's own dir). Other $(...) tokens are flagged
    # Unresolved (FullPath = $null) so callers can warn rather than guess.
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$ProjectFile)
    $dir = Split-Path -Parent (Resolve-FullPath $ProjectFile)
    $xml = Read-ProjectXml -Path $ProjectFile
    $out = @()
    foreach ($n in $xml.SelectNodes('//*[local-name()="Import"]')) {
        $proj = $n.GetAttribute('Project')
        if ([string]::IsNullOrWhiteSpace($proj)) { continue }
        $expanded = $proj -replace '\$\(MSBuildThisFileDirectory\)', ($dir + [System.IO.Path]::DirectorySeparatorChar)
        if ($expanded -match '\$\(') {
            $out += [pscustomobject]@{ Raw = $proj; FullPath = $null; Unresolved = $true }
            continue
        }
        $abs = [System.IO.Path]::GetFullPath((Join-Path $dir $expanded))
        $out += [pscustomobject]@{ Raw = $proj; FullPath = $abs; Unresolved = $false }
    }
    return $out
}

function Set-RawImportValue {
    # Precise, formatting-preserving rewrite of a single Import's Project attribute value in a
    # file's raw text (handles both quote styles), preserving the file's UTF-8 BOM state.
    # Not a blind regex: we replace the exact Project="<oldvalue>" token captured from the XML.
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$File,
        [Parameter(Mandatory)][string]$OldValue,
        [Parameter(Mandatory)][string]$NewValue
    )
    $full = Resolve-FullPath $File
    $bytes = [System.IO.File]::ReadAllBytes($full)
    $hasBom = ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF)
    $text = [System.IO.File]::ReadAllText($full)
    $changed = $false
    foreach ($q in @('"', "'")) {
        $needle = "Project=$q$OldValue$q"
        if ($text.Contains($needle)) {
            $text = $text.Replace($needle, "Project=$q$NewValue$q")
            $changed = $true
        }
    }
    if ($changed) {
        [System.IO.File]::WriteAllText($full, $text, (New-Object System.Text.UTF8Encoding($hasBom)))
    }
    return $changed
}

function Set-RawFileReplacement {
    # Replace an exact literal substring in a file's raw text, preserving UTF-8 BOM state.
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$File,
        [Parameter(Mandatory)][string]$Old,
        [Parameter(Mandatory)][string]$New
    )
    $full = Resolve-FullPath $File
    $bytes = [System.IO.File]::ReadAllBytes($full)
    $hasBom = ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF)
    $text = [System.IO.File]::ReadAllText($full)
    if (-not $text.Contains($Old)) { return $false }
    $text = $text.Replace($Old, $New)
    [System.IO.File]::WriteAllText($full, $text, (New-Object System.Text.UTF8Encoding($hasBom)))
    return $true
}

function Get-NewImportRaw {
    # Compute the new raw Project value from $ImporterDir to $TargetAbs, preserving a
    # $(MSBuildThisFileDirectory) prefix if the original used one.
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$ImporterDir,
        [Parameter(Mandatory)][string]$TargetAbs,
        [Parameter(Mandatory)][AllowEmptyString()][string]$OldRaw
    )
    $rel = Get-RelativePathSafe -From $ImporterDir -To $TargetAbs
    if ($OldRaw -match '^\$\(MSBuildThisFileDirectory\)') {
        return '$(MSBuildThisFileDirectory)' + $rel
    }
    return $rel
}