build/MicrobuildTest.ps1 (334 lines of code) (raw):
param (
[Parameter(Mandatory = $false)]
[string] $CPVSDrop
)
function CombineAndNormalize([string[]] $paths) {
$combined = [System.IO.Path]::Combine($paths)
return [System.IO.Path]::GetFullPath($combined)
}
function Log ($a) {
Write-Host `n
Write-Host $a.ToString()
}
function Test-AssemblyStrongNamed($assemblyPath) {
$hasPublicKey = $true
try {
$hasPublicKey = [System.Reflection.Assembly]::ReflectionOnlyLoadFrom($assemblyPath).GetName().GetPublicKeyToken().Count -gt 0
}
catch {
if (-Not $_.Exception.Message.Contains("It cannot be loaded from a new location within the same appdomain")) {
throw
}
}
return $hasPublicKey
}
class BuildInstance {
static $languages = @("cs", "de", "en", "es", "fr", "it", "ja", "ko", "pl", "pt-BR", "ru", "tr", "zh-Hans", "zh-Hant")
[string] $Root
[string[]] $AssemblyNames = @(
"Microsoft.Build.dll",
"Microsoft.Build.Framework.dll",
"Microsoft.Build.Tasks.Core.dll",
"Microsoft.Build.Utilities.Core.dll"
)
[string[]] $SatelliteAssemblyNames = @(
"Microsoft.Build.Conversion.Core.resources.dll",
"Microsoft.Build.Engine.resources.dll",
"Microsoft.Build.resources.dll",
"Microsoft.Build.Tasks.Core.resources.dll",
"Microsoft.Build.Utilities.Core.resources.dll",
"MSBuild.resources.dll",
"MSBuildTaskHost.resources.dll"
)
BuildInstance([String] $root) {
$this.Root = $root
}
[string[]] BuildFiles() {
return $this.ResolvedAssemblies() + $this.ResolvedSatelliteAssemblies()
}
[string[]] ResolvedAssemblies() {
return $this.AssemblyNames | foreach{CombineAndNormalize($this.Root, $_)}
}
[string[]] ResolvedSatelliteAssemblies() {
$satellites = @()
if (-Not ($this.IsLocalized())) {
return $satellites
}
foreach ($l in [BuildInstance]::languages) {
foreach ($s in $this.SatelliteAssemblyNames) {
$satellites += CombineAndNormalize(@($this.Root, $l, $s))
}
}
return $satellites
}
[bool] IsLocalized() {
return $this.Root -match ".*(x86|x64).*"
}
[String] ToString() {
return $this.Root + "`n`n" + (($this.BuildFiles() | foreach{"`t`t" + $_.ToString()}) -join "`n")
}
Check([Checker] $checker) {
$checker.Check($this)
}
}
class FullFrameworkBuildInstance : BuildInstance{
FullFrameworkBuildInstance([String] $root) : base ($root) {
([BuildInstance]$this).AssemblyNames += @(
"MSBuild.exe",
"MSBuildTaskHost.exe",
"Microsoft.Build.Conversion.Core.dll",
"Microsoft.Build.Engine.dll"
)
}
}
class NetCoreBuildInstance : BuildInstance{
NetCoreBuildInstance([String] $root) : base ($root) {
([BuildInstance]$this).AssemblyNames += "MSBuild.dll"
}
}
class NugetPackage{
[String] $Path
NugetPackage([string] $Path) {
$this.Path = $Path
}
Check([Checker] $checker) {
$checker.Check($this)
}
[string] ToString() {
return "NugetPackage: $($this.Path)"
}
}
class VsixPackage{
static [string] $PackageName = "Microsoft.Build.vsix"
[String] $Path
VsixPackage([string] $path) {
$this.Path = CombineAndNormalize($path, [VsixPackage]::PackageName)
}
Check([Checker] $checker) {
$checker.Check($this)
}
[string] ToString() {
return "Vsix package: $($this.Path)"
}
}
class Layout {
[String] $FFx86;
[String] $FFx64;
[String] $FFAnyCPU;
[String] $CoreAnyCPU;
[String] $NugetPackagePath;
[String] $VsixPath;
[BuildInstance[]] $BuildInstances
[NugetPackage[]] $NugetPackages
[VsixPackage] $VsixPackage
Layout($FFx86, $FFx64, $FFAnyCPU, $CoreAnyCPU, $NugetPackagePath, $VsixPath) {
$this.FFx86 = $FFx86
$this.FFx64 = $FFx64
$this.FFAnyCPU = $FFAnyCPU
$this.CoreAnyCPU = $CoreAnyCPU
$this.NugetPackagePath = $NugetPackagePath
$this.VsixPath = $VsixPath
$this.BuildInstances = @(
[FullFrameworkBuildInstance]::new($this.FFx86),
[FullFrameworkBuildInstance]::new($this.FFx64),
[FullFrameworkBuildInstance]::new($this.FFAnyCPU),
[NetCoreBuildInstance]::new($this.CoreAnyCPU)
)
$this.NugetPackages = Get-ChildItem $nugetPackagePath *.nupkg | foreach {[NugetPackage]::new($_.FullName)}
$this.VsixPackage = [VsixPackage]::new($VsixPath)
}
[String] ToString() {
$instances = ($this.BuildInstances | foreach{ "`t" + $_.ToString() }) -join "`n`n"
$nugets = ($this.NugetPackages | foreach{"`t" + $_.ToString()}) -join "`n"
return "Build Instances:`n$instances`n`nNuget Packages`n$($nugets)`n`n$($this.VsixPackage.ToString())"
}
static [Layout] FromMicrobuild() {
$root = $env:BUILD_REPOSITORY_LOCALPATH
$layout = [Layout]::new(
(CombineAndNormalize @($root, $env:FFBINPATH86)),
(CombineAndNormalize @($root, $env:FFBINPATH64)),
(CombineAndNormalize @($root, $env:BINPATH)),
(CombineAndNormalize @($root, $env:BINPATHNETCORE)),
(CombineAndNormalize @($root, $env:NUGETPACKAGESPATH)),
(CombineAndNormalize @($root, $env:SETUPLAYOUTPATH)))
return $layout
}
static [Layout] FromCPVSDrop([string] $root) {
$layout = [Layout]::new(
(CombineAndNormalize @($root, "bin\x86\Windows_NT\Release\Output")),
(CombineAndNormalize @($root, "bin\x64\Windows_NT\Release\Output")),
(CombineAndNormalize @($root, "bin\Release\Output")),
(CombineAndNormalize @($root, "bin\Release-NetCore\Output")),
(CombineAndNormalize @($root, "bin\Packages")),
(CombineAndNormalize @($root, "pkg")))
return $layout
}
Check([Checker] $checker) {
$checker.Check($this)
$this.BuildInstances | foreach {$_.Check($checker)}
$this.NugetPackages | foreach {$_.Check($checker)}
$this.VsixPackage.Check($checker)
}
}
class Diagnostic{
[String] $Type
[String] $Message
Diagnostic([String] $type, [String] $message) {
$this.Type = $type
$this.Message = $message
}
}
class Checker{
[Diagnostic[]] $Diagnostics = @()
Check($obj) {
$diags = $this.HandleObject($obj)
if ($diags -ne $null) {
$this.Diagnostics += $diags
}
}
[Diagnostic[]] HandleObject($obj) {
$type = $obj.GetType()
$diags = @()
if ($type -eq [Layout]) {
$diags = $this.CheckLayout($obj)
}
elseif ($type -eq [FullFrameworkBuildInstance]) {
$diags = $this.CheckFullFrameworkBuildInstance($obj)
}
elseif ($type -eq [NetCoreBuildInstance]) {
$diags = $this.CheckNetCoreBuildInstance($obj)
}
elseif ($type -eq [NugetPackage]) {
$diags = $this.CheckNugetPackage($obj)
}
elseif ($type -eq [VsixPackage]) {
$diags = $this.CheckVSixPackage($obj)
}
else {
$diags = $this.HandleUnknownType($obj, $type)
}
return $diags
}
[Diagnostic[]] HandleUnknownType($obj, [Type] $type) {
throw [System.NotImplementedException]
}
[Diagnostic[]] CheckLayout([Layout] $l) {return @()}
[Diagnostic[]] CheckFullFrameworkBuildInstance([FullFrameworkBuildInstance] $b) {return @()}
[Diagnostic[]] CheckNetCoreBuildInstance([NetCoreBuildInstance] $b) {return @()}
[Diagnostic[]] CheckNugetPackage([NugetPackage] $n) {return @()}
[Diagnostic[]] CheckVSixPackage([VsixPackage] $v) {return @()}
[Diagnostic] NewDiagnostic([String] $message) {
return [Diagnostic]::new($this.GetType().Name, $message)
}
}
class TestChecker : Checker{
[Diagnostic[]] CheckLayout([Layout] $l) {return $this.NewDiagnostic("Checked Layout")}
[Diagnostic[]] CheckFullFrameworkBuildInstance([FullFrameworkBuildInstance] $b) {return $this.NewDiagnostic("Checked FF Build Instance: $($b.Root)")}
[Diagnostic[]] CheckNetCoreBuildInstance([NetCoreBuildInstance] $b) {return $this.NewDiagnostic("Checked Core Build Instance: $($b.Root)")}
[Diagnostic[]] CheckNugetPackage([NugetPackage] $n) {return $this.NewDiagnostic("Checked Nuget Package: $($n.Path)")}
[Diagnostic[]] CheckVSixPackage([VsixPackage] $v) {return $this.NewDiagnostic("Checked VsixPackage: $($v.Path)")}
}
class FileChecker : Checker{
$ExpectedNumberOfNugetPackages = 7
[Diagnostic[]] CheckPathExists([string] $path) {
if (-Not (Test-Path $path)) {
return $this.NewDiagnostic("Path does not exist: $path");
}
return @()
}
[Diagnostic[]] CheckLayout([Layout] $l) {
$diags = @()
$diags += $this.CheckPathExists($l.FFx86)
$diags += $this.CheckPathExists($l.FFx64)
$diags += $this.CheckPathExists($l.FFAnyCPU)
$diags += $this.CheckPathExists($l.CoreAnyCPU)
$diags += $this.CheckPathExists($l.NugetPackagePath)
$diags += $this.CheckPathExists($l.VsixPath)
if ($l.NugetPackages.Count -ne $this.ExpectedNumberOfNugetPackages) {
$diags += $this.NewDiagnostic("There should be $($this.ExpectedNumberOfNugetPackages) nuget packages in $($l.NugetPackagePath) but $($l.NugetPackages.Count) were found")
}
return $diags
}
[Diagnostic[]] CheckBuildInstance([BuildInstance] $b) {
return $b.BuildFiles() | foreach{$this.CheckPathExists($_)}
}
[Diagnostic[]] CheckFullFrameworkBuildInstance([FullFrameworkBuildInstance] $b) {
return $this.CheckBuildInstance($b)
}
[Diagnostic[]] CheckNetCoreBuildInstance([NetCoreBuildInstance] $b) {
return $this.CheckBuildInstance($b)
}
[Diagnostic[]] CheckNugetPackage([NugetPackage] $n) {
return $this.CheckPathExists($n.Path)
}
[Diagnostic[]] CheckVSixPackage([VsixPackage] $v) {
return $this.CheckPathExists($v.Path)
}
}
class RealSignedChecker : Checker {
[Diagnostic[]] CheckIsSigned([String] $assembly) {
if (-Not (Test-Path $assembly)) {
return @()
}
$signature = Get-AuthenticodeSignature $assembly
$looksRealSigned = $signature.Status -eq [System.Management.Automation.SignatureStatus]::Valid
$looksRealSigned = $looksRealSigned -and ($signature.SignatureType -eq [System.Management.Automation.SignatureType]::Authenticode)
$looksRealSigned = $looksRealSigned -and ($signature.SignerCertificate.Issuer -match ".*Microsoft.*Redmond.*")
$looksRealSigned = $looksRealSigned -and (-not ($signature.SignerCertificate.Issuer -match "Test"))
if (-Not $looksRealSigned) {
$strongNamed = Test-AssemblyStrongNamed($assembly)
return $this.NewDiagnostic("Assembly not real signed: $assembly.`nStrong named: $strongNamed; CertificateIssuer: [$($signature.SignerCertificate.Issuer)]")
}
return @()
}
[Diagnostic[]] CheckBuildInstance([BuildInstance] $b) {
return $b.BuildFiles() | foreach{$this.CheckIsSigned($_)}
}
[Diagnostic[]] CheckFullFrameworkBuildInstance([FullFrameworkBuildInstance] $b) {
return $this.CheckBuildInstance($b)
}
[Diagnostic[]] CheckNetCoreBuildInstance([NetCoreBuildInstance] $b) {
return $this.CheckBuildInstance($b)
}
}
class NugetVersionChecker : Checker {
[Diagnostic[]] CheckNugetPackage([NugetPackage] $n) {
$packageNameRegex = "Microsoft\.Build\..*\d+\.\d+\.\d+.*\.nupkg"
$packageName = [System.IO.Path]::GetFileName($n.Path)
if (-Not ($packageName -match $packageNameRegex)) {
return $this.NewDiagnostic("Package `"$packageName`" does not match regex `"$packageNameRegex`"")
}
return @()
}
}
[String[]] $diagnostics = @()
$layout = $null
if ($CPVSDrop) {
Log "Used `$CPVSDrop=$CPVSDrop"
$layout = [Layout]::FromCPVSDrop($CPVSDrop)
}
else {
Log "Running inside microbuild environment"
$layout = [Layout]::FromMicrobuild()
}
Log $layout
# $checkers = @([TestChecker]::new())
$checkers = @(
[FileChecker]::new(),
[RealSignedChecker]::new(),
[NugetVersionChecker]::new()
)
$checkers | foreach{$layout.Check($_)}
$diagnosticCount = 0
Log "Failed checks:"
foreach ($checker in $checkers) {
$diags = $checker.Diagnostics
$diagnosticCount += $diags.Count
$diags | foreach{Log "$($_.Type): $($_.Message)"}
}
if ($diagnosticCount -eq 0) {
Log "No failed checks"
}
else {
Throw "$diagnosticCount failed checks"
}