Netscoot.Unity/Public/Test-UnityMetaIntegrity.ps1

function Test-UnityMetaIntegrity {
    <#
    .SYNOPSIS
        Report Unity .meta integrity problems under a root: Assets missing a .meta, and
        orphan .meta files whose asset is gone. These are the Unity analog of dangling
        references - both lead to broken/regenerated GUIDs.
 
    .DESCRIPTION
        Walks the tree and pairs every asset (file or folder) with its `<name>.meta`.
        Emits one object per problem and surfaces it through the standard streams so behavior
        follows invocation: By default it writes a Warning per problem; -Strict escalates each to
        a non-terminating error (honoring -ErrorAction). Objects are always emitted so results are
        capturable/filterable.
 
        Ignores Unity-hidden entries (names starting with '.', folders ending with '~')
        and the Library/Temp/obj caches.
 
    .PARAMETER Root
        Folder to scan (typically an 'Assets' folder). Accepts pipeline input. Defaults to
        the current directory.
 
    .PARAMETER Strict
        Escalate problems from warnings to non-terminating errors.
 
    .OUTPUTS
        Netscoot.MetaIntegrity - one per problem.
 
    .EXAMPLE
        Test-UnityMetaIntegrity -Root ./Assets -Strict
 
        Reports MissingMeta and OrphanMeta under Assets, one non-terminating error each.
    #>

    [CmdletBinding()]
    [OutputType('Netscoot.MetaIntegrity')]
    param(
        [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('FullName', 'Path', 'PSPath')]
        [string]$Root,
        [switch]$Strict
    )

    process {
        if (-not $Root) { $Root = (Get-Location).Path }
        $Root = Resolve-FullPath $Root

        # Exclude Unity caches anchored at the scan root (not "Temp" anywhere - the OS temp
        # dir itself contains that segment), plus .git and Unity-hidden entries.
        $rootLen = $Root.TrimEnd('\', '/').Length
        $entries = Get-ChildItem -LiteralPath $Root -Recurse -Force -ErrorAction SilentlyContinue |
            Where-Object {
                $rel = $_.FullName.Substring($rootLen)
                $rel -notmatch '^[\\/](Library|Temp|obj)[\\/]' -and
                $_.FullName -notmatch '[\\/]\.git[\\/]' -and
                $_.Name -notlike '.*' -and $_.Name -notlike '*~'
            }

        foreach ($e in $entries) {
            if ($e.Name -like '*.meta') {
                # Orphan check: the asset this .meta describes should exist.
                $asset = $e.FullName.Substring(0, $e.FullName.Length - '.meta'.Length)
                if (-not (Test-Path -LiteralPath $asset)) {
                    $rec = [pscustomobject]@{ PSTypeName = 'Netscoot.MetaIntegrity'; Kind = 'OrphanMeta'; Path = $e.FullName }
                    $msg = "Orphan .meta (no matching asset): $($e.FullName)"
                    if ($Strict) { Write-Error -Message $msg -Category InvalidData -TargetObject $rec -ErrorId 'OrphanMeta' } else { Write-Warning $msg }
                    $rec
                }
            } else {
                # Missing-meta check: every asset/folder should have a sibling .meta.
                if (-not (Test-Path -LiteralPath "$($e.FullName).meta" -PathType Leaf)) {
                    $rec = [pscustomobject]@{ PSTypeName = 'Netscoot.MetaIntegrity'; Kind = 'MissingMeta'; Path = $e.FullName }
                    $msg = "Asset has no .meta (Unity will generate a new GUID): $($e.FullName)"
                    if ($Strict) { Write-Error -Message $msg -Category InvalidData -TargetObject $rec -ErrorId 'MissingMeta' } else { Write-Warning $msg }
                    $rec
                }
            }
        }
    }
}