I spent some of my weekend determining whether the IL changed between builds. The following script builds a solution, generates intermediate language from the build result, filters out stuff that always changes (such as the MVID), and creates a hash of the result. If two hashes are the same, then the two builds resulted in functionally identical results. This is useful for verifying automated formatting.

function Get-IntermediateLanguageHash ([string]$SolutionPath) {

    $timestamp = (Get-Date).Ticks;
    $msbuildOutputPath = "$env:TMP/ByteCodeHash.$timestamp";

    Write-Host "Building $SolutionPath to $msbuildOutputPath";

    New-Item -ItemType Directory $msbuildOutputPath | Out-Host;

    # Build the solution.
    msbuild $SolutionPath `
        -p:Platform=x64 `
        -p:Configuration=Release `
        -p:OutputPath=$msbuildOutputPath `
        -verbosity:minimal `
        -restore | Out-Host;

    $dotNetBinaries = Get-ChildItem $msbuildOutputPath -File -Recurse `
    | Where-Object { $_.Extension -match "exe|dll" } `
    | Select-Object -ExpandProperty FullName;

    Write-Host "Hashing IL output of $($dotNetBinaries.Count) files in $msbuildOutputPath.";

    # Covert each to IL.
    $ildasmExe = "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\ildasm.exe";
    $intermediateLanguageFilePaths = $dotNetBinaries | ForEach-Object {
        $inputPath = $_;
        $ildasmOutputPath = "$($inputPath).il.filtered";

        # If there is an error, we probably have a .NET Core executable.
        # See https://stackoverflow.com/a/60590779/1108891
        
        . $ildasmExe $inputPath /text /utf8 2>$null `
        | Where-Object { 
            $_ -notmatch '// Image base: 0x' `
            -and $_ -notmatch $timestamp `
            -and $_ -notmatch '// MVID:';
        } `
        | Add-Content $ildasmOutputPath;

        return $ildasmOutputPath;
    }

    # Hash the content.
    $stringAsStream = [System.IO.MemoryStream]::new();
    $writer = [System.IO.StreamWriter]::new($stringAsStream);
    Get-Content $intermediateLanguageFilePaths `
    | ForEach-Object {
        $line = $_;
        $writer.Write($line);
    }

    $writer.Flush();
    $stringAsStream.Position = 0;
    $result = Get-FileHash -InputStream $stringAsStream;

    return $result.Hash;
}