Introduction

Last week I found a little bug caused by Anotar. Anotar adds IL code to my assembly. The problem with this bug is that it only manifests itself at runtime or when covered by a test or when doing a PEVerify. Since I don’t claim to have 100% test coverage. I need a way to run PEVErify on my assemblies to make sure I catch any Anotar bugs (So I can report them and Simon can fix them).

When I mentioned this on twitter, the always brilliant Simon Cropp pointed me to his Verifier class that you can find in the sourcecode of Anotar.

The code

And here is my current, slightly reworked version of his code.

vb.net
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Imports System
Imports System.Diagnostics
Imports System.IO
Imports NUnit.Framework
 
Namespace PeVerify
    Public Class Verifier
 
        Public Shared Sub Verify(assemblyPath As String, Optional ByVal errorCodesToIgnore As String = "")
            Dim before = Validate(assemblyPath, errorCodesToIgnore)
            Assert.IsTrue(File.Exists(assemblyPath), "Assembly not found: " & assemblyPath)
            Assert.IsFalse(before.Contains("Error"), before)
        End Sub
 
        Private Shared Function Validate(assemblyPath As String, Optional ByVal errorCodesToIgnore As String = "") As String
            Dim exePath = GetPathToPeVerify()
            If (Not File.Exists(exePath)) Then
                Return String.Empty
            End If
            Dim arguments = String.Format("""{0}""", assemblyPath)
            If Not String.IsNullOrEmpty(errorCodesToIgnore) Then
                arguments = String.Format("{0} /ignore={1}", arguments, errorCodesToIgnore)
            End If
            Dim process1 = Process.Start(New ProcessStartInfo(exePath, arguments) With {.RedirectStandardOutput = True, .UseShellExecute = False, .CreateNoWindow = True})
            process1.WaitForExit(10000)
            Return process1.StandardOutput.ReadToEnd().Trim().Replace(assemblyPath, "")
        End Function
 
        Private Shared Function GetPathToPeVerify() As String
            Dim exePath = Environment.ExpandEnvironmentVariables("%programfiles(x86)%Microsoft SDKsWindowsv7.0ABinNETFX 4.0 ToolsPEVerify.exe")
            If (Not File.Exists(exePath)) Then
                exePath = Environment.ExpandEnvironmentVariables("%programfiles(x86)%Microsoft SDKsWindowsv8.0ABinNETFX 4.0 ToolsPEVerify.exe")
            End If
            Return exePath
        End Function
        
    End Class
End Namespace
Imports System
Imports System.Diagnostics
Imports System.IO
Imports NUnit.Framework

Namespace PeVerify
    Public Class Verifier

        Public Shared Sub Verify(assemblyPath As String, Optional ByVal errorCodesToIgnore As String = "")
            Dim before = Validate(assemblyPath, errorCodesToIgnore)
            Assert.IsTrue(File.Exists(assemblyPath), "Assembly not found: " & assemblyPath)
            Assert.IsFalse(before.Contains("Error"), before)
        End Sub

        Private Shared Function Validate(assemblyPath As String, Optional ByVal errorCodesToIgnore As String = "") As String
            Dim exePath = GetPathToPeVerify()
            If (Not File.Exists(exePath)) Then
                Return String.Empty
            End If
            Dim arguments = String.Format("""{0}""", assemblyPath)
            If Not String.IsNullOrEmpty(errorCodesToIgnore) Then
                arguments = String.Format("{0} /ignore={1}", arguments, errorCodesToIgnore)
            End If
            Dim process1 = Process.Start(New ProcessStartInfo(exePath, arguments) With {.RedirectStandardOutput = True, .UseShellExecute = False, .CreateNoWindow = True})
            process1.WaitForExit(10000)
            Return process1.StandardOutput.ReadToEnd().Trim().Replace(assemblyPath, "")
        End Function

        Private Shared Function GetPathToPeVerify() As String
            Dim exePath = Environment.ExpandEnvironmentVariables("%programfiles(x86)%Microsoft SDKsWindowsv7.0ABinNETFX 4.0 ToolsPEVerify.exe")
            If (Not File.Exists(exePath)) Then
                exePath = Environment.ExpandEnvironmentVariables("%programfiles(x86)%Microsoft SDKsWindowsv8.0ABinNETFX 4.0 ToolsPEVerify.exe")
            End If
            Return exePath
        End Function
        
    End Class
End Namespace

This does not work on either the ncrunch or resharper testrunner since those use their ownversion of the assembly. But it works on Teamcity, which is the important part for me since I don’t want to let bugs out in the wild.

You can use it like this.

vb.net
1
2
3
4
5
<Test>
        Public Sub PeVerifyAssembly()
            Dim filePath = Assembly.GetAssembly(GetType(SomeTypeInYourAssembly)).Location
            Verifier.Verify(filePath)
        End Sub
<Test>
        Public Sub PeVerifyAssembly()
            Dim filePath = Assembly.GetAssembly(GetType(SomeTypeInYourAssembly)).Location
            Verifier.Verify(filePath)
        End Sub

Or in one case where I had an error like this you can just ignore the cannot find file error.

[IL]: Error: [ : TexDatabases2012.Menu.Facade.MessagesFacade::set__newtransfers] [HRESULT 0x80070002] – The system cannot find the file specified. [

vb.net
1
2
3
4
5
<Test>
        Public Sub PeVerifyAssembly()
            Dim filePath = Assembly.GetAssembly(GetType(SomeTypeInYourAssembly)).Location
            Verifier.Verify(filePath, "0x80070002")
        End Sub
<Test>
        Public Sub PeVerifyAssembly()
            Dim filePath = Assembly.GetAssembly(GetType(SomeTypeInYourAssembly)).Location
            Verifier.Verify(filePath, "0x80070002")
        End Sub

Conclusion

Adding a PEVerify step when working with software like Fody seems to be a good thing.

And I would also like to thank Simon for his awesome support.