<# .SYNOPSIS Puts back the Roslyn script compiler that Windows Defender quarantines out of the Unity package cache, so the editor can compile the project at all. .DESCRIPTION Unity 2020.3 compiles C# by launching an *external* compiler, and because this project installs `com.unity.roslyn` (a transitive dependency of `com.unity.entities`) that compiler is the one shipped inside the package: \Library\PackageCache\com.unity.roslyn@\Compiler~\win64\csc.exe Defender quarantines unsigned executables out of Unity package folders on this machine - the recorded casualties are `com.unity.burst\.Runtime\hostwin\llvm-lipo.exe` and `dsymutil.exe` (heuristic PUA signatures, ThreatID 2147972435) and, for this project, that `csc.exe`. It has to be deleted rather than absent from the package: the registry tarball does ship it (`package/Compiler~/win64/csc.exe`). With it gone, Unity logs the same failure for every assembly it was about to build: Starting external compile of Unity.Jobs.CodeGen.dll: ...\Compiler~\win64\csc.exe ... Exception when running external compiler: System.ComponentModel.Win32Exception (0x80004005): Native error= The system cannot find the file specified. and then, because nothing compiled at all, reports the misleading Unable to resolve reference 'Unity.Mathematics' / 'Unity.Collections' / ... for every `com.recroom.*` assembly, while `Library\ScriptAssemblies` stays empty apart from `BuiltinAssemblies.stamp`. So: an empty ScriptAssemblies folder plus a wall of unresolvable references means "the compiler never ran", not "the packages are broken". This script restores the file from `-FromPath`, or from the Unity editor install's own `Editor\Data\Tools\Roslyn\csc.exe` (a different build of the same 3.8 apphost, verified to drive the package's newer `csc.dll`), and then proves it runs by asking it for its version. The Unity-install copy is 3.8.0-dev.20527.1 as well, so the compiler behaviour is unchanged; only the ~170 KB launcher differs. Prefer `-FromPath` with the authentic file if you have the package tarball to hand: https://cdn.packages.unity.com/tarballs/com.unity.roslyn/com.unity.roslyn-0.2.2-preview/8b5b337db92cbdec37b54235ec8b826c92c6ddef.tgz (`tar -xzf package/Compiler~/win64/csc.exe`, md5 8c4361cc37113f8467feb1d22b4e83da) This is a repair, not a cure: Defender will do it again on the next full scan, and a Unity package re-resolve copies the damaged file back out of the shared cache. Add the exclusions this script prints at the end, and re-run with -IncludeGlobalCache, to close both loops. .PARAMETER ProjectRoot Candidate project roots, tried in order; the first one that exists and holds the roslyn package cache is used. .PARAMETER FromPath Use this csc.exe instead of the Unity editor install's copy. .PARAMETER IncludeGlobalCache Also repair the shared package cache under %LOCALAPPDATA%\Unity\cache\packages, which is where a re-resolve would otherwise copy the missing file back from. .PARAMETER Force Overwrite csc.exe even when one is already present and working. .EXAMPLE pwsh -File tools/repair-unity-script-compiler.ps1 pwsh -File tools/repair-unity-script-compiler.ps1 -FromPath C:\tmp\csc.exe -IncludeGlobalCache -Force #> [CmdletBinding()] param( [string[]] $ProjectRoot = @( 'C:\RecRoom\Documents\EggFiles\ProjectTemplate', 'C:\RecRoom\OneDrive\Documents\EggFiles\ProjectTemplate' ), [string] $FromPath, [switch] $IncludeGlobalCache, [switch] $Force ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest $expectedMd5 = '8c4361cc37113f8467feb1d22b4e83da' function Write-Status { param([string] $Level, [string] $Message) Write-Host ("[{0}] {1}" -f $Level, $Message) } function Find-RoslynCompilerDir { <# The roslyn package folder inside a project, or $null. #> param([string] $Root) $pkgRoot = Join-Path $Root 'Library\PackageCache' if (-not (Test-Path -LiteralPath $pkgRoot)) { return $null } $dirs = Get-ChildItem -LiteralPath $pkgRoot -Directory -Filter 'com.unity.roslyn@*' -ErrorAction SilentlyContinue | ForEach-Object { Join-Path $_.FullName 'Compiler~\win64' } | Where-Object { Test-Path -LiteralPath $_ } if (-not $dirs) { return $null } return @($dirs)[0] } function Find-UnityInstallCompiler { <# Tools\Roslyn\csc.exe from the editor version this project is pinned to. #> param([string] $Root) $versionFile = Join-Path $Root 'ProjectSettings\ProjectVersion.txt' $version = $null if (Test-Path -LiteralPath $versionFile) { $line = Get-Content -LiteralPath $versionFile | Where-Object { $_ -match '^m_EditorVersion:' } | Select-Object -First 1 if ($line) { $version = ($line -split ':', 2)[1].Trim() } } $roots = @() if ($version) { $roots += "C:\Program Files\Unity $version" $roots += "C:\Program Files\Unity\Hub\Editor\$version" } $roots += @(Get-ChildItem 'C:\Program Files\Unity*' -Directory -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName }) foreach ($r in $roots) { $candidate = Join-Path $r 'Editor\Data\Tools\Roslyn\csc.exe' if (Test-Path -LiteralPath $candidate) { return $candidate } } return $null } function Test-Compiler { <# Runs the compiler's own version probe: the launcher only works if it can reach its csc.dll. #> param([string] $Path) try { $output = & $Path -version 2>&1 } catch { return [pscustomobject]@{ Ok = $false; Detail = $_.Exception.Message } } $text = ($output | Out-String).Trim() if ($LASTEXITCODE -ne 0 -or $text -notmatch 'Visual C# Compiler version|^\d+\.\d+') { $detail = if ($text) { $text } else { "exited with code $LASTEXITCODE and no output" } return [pscustomobject]@{ Ok = $false; Detail = $detail } } return [pscustomobject]@{ Ok = $true; Detail = $text } } function Repair-CompilerDir { param([string] $Dir, [string] $Source) $target = Join-Path $Dir 'csc.exe' if ((Test-Path -LiteralPath $target) -and -not $Force) { $probe = Test-Compiler -Path $target if ($probe.Ok) { Write-Status 'PASS' "already present and working: $target ($($probe.Detail))" return $false } Write-Status 'WARN' "present but not usable ($($probe.Detail)) - replacing it" } if (-not (Test-Path -LiteralPath $Dir)) { New-Item -ItemType Directory -Path $Dir -Force | Out-Null } Copy-Item -LiteralPath $Source -Destination $target -Force $probe = Test-Compiler -Path $target if (-not $probe.Ok) { throw "Restored $target but it still does not run: $($probe.Detail)" } $md5 = (Get-FileHash -LiteralPath $target -Algorithm MD5).Hash.ToLowerInvariant() if ($md5 -eq $expectedMd5) { Write-Status 'PASS' "restored the authentic $target ($md5)" } else { Write-Status 'PASS' "restored $target (md5 $md5; the Unity install build, not the package one)" } return $true } # ---- main ------------------------------------------------------------------------- Write-Host '' $project = $null foreach ($candidate in $ProjectRoot) { if (-not (Test-Path -LiteralPath $candidate)) { continue } if (Find-RoslynCompilerDir -Root $candidate) { $project = $candidate; break } } if (-not $project) { throw "No project with an extracted com.unity.roslyn package found. Tried:`n $($ProjectRoot -join "`n ")" } $compilerDir = Find-RoslynCompilerDir -Root $project Write-Status 'INFO' "project : $project" Write-Status 'INFO' "compiler : $compilerDir" $source = $FromPath if ($source) { if (-not (Test-Path -LiteralPath $source)) { throw "-FromPath '$source' does not exist." } Write-Status 'INFO' "source : $source (-FromPath)" } else { $source = Find-UnityInstallCompiler -Root $project if (-not $source) { throw @" No csc.exe available to restore from. Either install the editor version named in ProjectSettings\ProjectVersion.txt, or pass -FromPath pointing at a copy of the package's win64\csc.exe taken from the registry tarball. "@ } Write-Status 'INFO' "source : $source (Unity editor install)" } $repaired = Repair-CompilerDir -Dir $compilerDir -Source $source if (-not $repaired) { Write-Status 'INFO' 'nothing to do for this project' } if ($IncludeGlobalCache) { $globalRoot = Join-Path $env:LOCALAPPDATA 'Unity\cache\packages\packages.unity.com' $globalDirs = Get-ChildItem -LiteralPath $globalRoot -Directory -Filter 'com.unity.roslyn@*' -ErrorAction SilentlyContinue | ForEach-Object { Join-Path $_.FullName 'Compiler~\win64' } if (-not $globalDirs) { Write-Status 'WARN' "no com.unity.roslyn under $globalRoot" } foreach ($d in $globalDirs) { Repair-CompilerDir -Dir $d -Source $source | Out-Null } } # Unity will not pick the compiler up until it recompiles; ScriptAssemblies should then fill in. $scriptAssemblies = Join-Path $project 'Library\ScriptAssemblies' if (Test-Path -LiteralPath $scriptAssemblies) { $built = @(Get-ChildItem -LiteralPath $scriptAssemblies -Filter '*.dll' -ErrorAction SilentlyContinue).Count if ($built -eq 0) { Write-Status 'INFO' 'Library\ScriptAssemblies is still empty - reopen the project (Unity recompiles on focus)' } else { Write-Status 'INFO' "Library\ScriptAssemblies holds $built assemblies" } } Write-Host '' Write-Status 'INFO' 'Windows Defender is what removes this file. In an ELEVATED PowerShell:' Write-Host " Add-MpPreference -ExclusionPath '$project'" Write-Host " Add-MpPreference -ExclusionPath `"$env:LOCALAPPDATA\Unity\cache`"" Write-Status 'INFO' 'the same rule also emptied com.unity.burst\.Runtime\hostwin (llvm-lipo/dsymutil, needed for Apple targets only)'