Windows/TestHarnesses/T1127.001_MSBuild/InvokeMSBuild.Tests.ps1

Set-StrictMode -Version Latest

$TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
$ModuleRoot = Resolve-Path "$TestScriptRoot\..\..\..\"
$ModuleManifest = "$ModuleRoot\AtomicTestHarnesses.psd1"

Remove-Module [A]tomicTestHarnesses
Import-Module $ModuleManifest -Force -ErrorAction Stop

Describe 'Invoke-ATHMSBuild' {
    BeforeAll {
        $Help = Get-Help -Name Invoke-ATHMSBuild -Full
    
        $ExpectedTechniqueID = $null

        if ($Help.Synopsis.Split("`r`n")[-1] -match '^(?-i:Technique ID: )(?<TechniqueID>\S+) (?<TechniqueDescription>\(.+\))$') {
            $ExpectedTechniqueID = $Matches['TechniqueID']
        }

        $FixedTestGuid = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
    }

    Context 'Validating error conditions' -Tag 'Unit', 'T1127.001' {
        It 'should not execute an executable that is not MSBuild' -Tag 'Unit', 'T1127.001' {
            { Invoke-ATHMSBuild -MSBuildFilePath "$Env:windir\System32\notepad.exe" -ErrorAction Stop } | Should -Throw
        }

        It 'should not accept a project file in a non-existent directory' -Tag 'Unit', 'T1127.001' {
            $BogusPath = 'C:\dsdfsiuhsdrfsawgfds\sdlfksdjflksdj'

            Test-Path -Path $BogusPath -PathType Container | Should -BeFalse

            { Invoke-ATHMSBuild -ProjectFilePath $BogusPath -ErrorAction Stop } | Should -Throw
        }

        It 'should not accept a custom engine DLL in a non-existent directory' -Tag 'Unit', 'T1127.001' {
            $BogusPath = 'C:\dsdfsiuhsdrfsawgfds\sdlfksdjflksdj'

            Test-Path -Path $BogusPath -PathType Container | Should -BeFalse

            { Invoke-ATHMSBuild -UseCustomTaskFactory -CustomEngineDllPath $BogusPath -ErrorAction Stop } | Should -Throw
        }

        It 'should not execute when more then one *proj file is present when no command-line args are supplied' -Tag 'Unit', 'T1127.001' {
            $ProjFile1 = New-Item -Path test_project_1.proj -Force -ErrorAction SilentlyContinue
            $ProjFile2 = New-Item -Path test_project_2.proj -Force -ErrorAction SilentlyContinue
            
            { Invoke-ATHMSBuild -NoCLIProjectFile -ErrorAction Stop } | Should -Throw

            Remove-Item -Path test_project_1.proj -Force -ErrorAction SilentlyContinue
            Remove-Item -Path test_project_2.proj -Force -ErrorAction SilentlyContinue
        }

        It 'should not accept a non-*proj file extension when no command-line args are supplied' -Tag 'Unit', 'T1127.001' {
            { Invoke-ATHMSBuild -ProjectFilePath test.txt -NoCLIProjectFile -ErrorAction Stop } | Should -Throw
        }

        It 'should indicate that the MSBuild runner process failed to start' -Tag 'Unit', 'T1127.001' {
            Mock Invoke-CimMethod { return @{ ReturnValue = 1 } }

            { Invoke-ATHMSBuild -ErrorAction Stop } | Should -Throw
        }

        It 'should indicate that the MSBuild child process failed to launch' -Tag 'Unit', 'T1127.001' {
            Mock Wait-Event { return $null }

            { Invoke-ATHMSBuild -ErrorAction Stop } | Should -Throw
        }

        It 'should accept custom project XML' -Tag 'Unit', 'T1127.001' {
            $ProjectXml = @"
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="TestTarget">
    <TestTask />
  </Target>
  <UsingTask TaskName="TestTask" TaskFactory="CodeTaskFactory" AssemblyFile="$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())Microsoft.Build.Tasks.v4.0.dll" >
    <Task>
      <Code Language="cs">
        <![CDATA[
        System.Console.WriteLine("Hello");
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>
"@


            $Result = Invoke-ATHMSBuild -ProjectFileContent $ProjectXml
            
            $Result | Should -Not -BeNullOrEmpty

            $Result.TechniqueID                   | Should -BeExactly $ExpectedTechniqueID
            $Result.TestSuccess                   | Should -BeNullOrEmpty
            $Result.TestGuid                      | Should -BeNullOrEmpty
            $Result.ExecutionType                 | Should -BeExactly 'CustomProjectFileContent'
            $Result.ProjectFilePath               | Should -Not -BeNullOrEmpty
            $Result.ProjectFileHashSHA256         | Should -Not -BeNullOrEmpty
            $Result.ProjectContents               | Should -BeExactly $ProjectXml
            $Result.CustomEnginePath              | Should -BeNullOrEmpty
            $Result.CustomEngineHashSHA256        | Should -BeNullOrEmpty
            $Result.RunnerFilePath                | Should -Not -BeNullOrEmpty
            $Result.RunnerProcessId               | Should -Not -BeNullOrEmpty
            $Result.RunnerCommandLine             | Should -Not -BeNullOrEmpty
            $Result.RunnerChildProcessId          | Should -BeNullOrEmpty
            $Result.RunnerChildProcessCommandLine | Should -BeNullOrEmpty
        }
    }

    Context 'Expected artifacts and behaviors when exercising the attack technique' -Tag 'Technique', 'T1127.001' {
        BeforeAll {
            $Script:DefaultMSBuildPath = "$([Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory())MSBuild.exe"
            $Script:AlternateMSBuildPath = "$env:windir\Temp\notepad.exe"

            Copy-Item -Path $Script:DefaultMSBuildPath -Destination $Script:AlternateMSBuildPath

            $Script:AlternateProjectFileName = 'test.txt'
        }

        It 'should compile and execute embedded .NET code (Language: <Language>, UseAlternateMSBuildPath: <UseAlternateMSBuildPath>, UseAlternateProjectFilename: <UseAlternateProjectFilename>, OmitProjectFromCLI: <OmitProjectFromCLI>)' -Tag 'Technique', 'T1127.001' {
            $Arguments = @{}

            if ($UseAlternateProjectFilename) {
                $ExpectedFileName = $AlternateProjectFileName

                $Arguments['ProjectFilePath'] = $ExpectedFileName
            } else {
                $ExpectedFileName = 'test.proj'
            }

            if ($UseAlternateMSBuildPath) {
                $ExpectedMSBuildFileName = $AlternateMSBuildPath.Split('\')[-1]
                $ExpectedMSBuildPath = $AlternateMSBuildPath

                $Arguments['MSBuildFilePath'] = $AlternateMSBuildPath
            } else {
                $ExpectedMSBuildFileName = 'MSBuild.exe'
                $ExpectedMSBuildPath = $DefaultMSBuildPath
            }

            if ($OmitProjectFromCLI) {
                $Arguments['NoCLIProjectFile'] = $True

                $IsCLIPresent = $False
            } else {
                $IsCLIPresent = $True
            }
            
            $Result = Invoke-ATHMSBuild -Language $Language -TestGuid $FixedTestGuid @Arguments
            
            $Result | Should -Not -BeNullOrEmpty

            $Result.TechniqueID                   | Should -BeExactly $ExpectedTechniqueID
            $Result.TestSuccess                   | Should -BeTrue
            $Result.TestGuid                      | Should -BeExactly $FixedTestGuid
            $Result.ExecutionType                 | Should -BeExactly 'InlineSourceCode'
            $Result.ProjectFilePath.EndsWith($ExpectedFileName) | Should -Be $True
            $Result.ProjectFileHashSHA256         | Should -Not -BeNullOrEmpty
            $Result.ProjectContents               | Should -Match "<Code Language=`"$Language`">"
            $Result.CustomEnginePath              | Should -BeNullOrEmpty
            $Result.CustomEngineHashSHA256        | Should -BeNullOrEmpty
            $Result.RunnerFilePath                | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath.ToLower()      | Should -BeExactly $ExpectedMSBuildPath.ToLower()
            $Result.RunnerProcessId               | Should -Not -BeNullOrEmpty
            $Result.RunnerCommandLine.EndsWith($ExpectedFileName) | Should -Be $IsCLIPresent
            $Result.RunnerChildProcessId          | Should -Not -BeNullOrEmpty
            $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid

            $Result
        } -TestCases @(
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; Language = 'cs' },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; Language = 'cs' },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; Language = 'cs' },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; Language = 'cs' },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; Language = 'cs' },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; Language = 'cs' },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; Language = 'vb' },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; Language = 'vb' },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; Language = 'vb' },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; Language = 'vb' },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; Language = 'vb' },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; Language = 'vb' },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; Language = 'js' },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; Language = 'js' },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; Language = 'js' },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; Language = 'js' },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; Language = 'js' },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; Language = 'js' }
        )

        It 'should execute embedded property function code (UseAlternateMSBuildPath: <UseAlternateMSBuildPath>, UseAlternateProjectFilename: <UseAlternateProjectFilename>, OmitProjectFromCLI: <OmitProjectFromCLI>)' -Tag 'Technique', 'T1127.001' {
            $Arguments = @{}

            if ($UseAlternateProjectFilename) {
                $ExpectedFileName = $AlternateProjectFileName

                $Arguments['ProjectFilePath'] = $ExpectedFileName
            } else {
                $ExpectedFileName = 'test.proj'
            }

            if ($UseAlternateMSBuildPath) {
                $ExpectedMSBuildFileName = $AlternateMSBuildPath.Split('\')[-1]
                $ExpectedMSBuildPath = $AlternateMSBuildPath

                $Arguments['MSBuildFilePath'] = $AlternateMSBuildPath
            } else {
                $ExpectedMSBuildFileName = 'MSBuild.exe'
                $ExpectedMSBuildPath = $DefaultMSBuildPath
            }

            if ($OmitProjectFromCLI) {
                $Arguments['NoCLIProjectFile'] = $True

                $IsCLIPresent = $False
            } else {
                $IsCLIPresent = $True
            }
            
            $Result = Invoke-ATHMSBuild -UsePropertyFunctions -TestGuid $FixedTestGuid @Arguments
            
            $Result | Should -Not -BeNullOrEmpty

            $Result.TechniqueID                   | Should -BeExactly $ExpectedTechniqueID
            $Result.TestSuccess                   | Should -BeTrue
            $Result.TestGuid                      | Should -BeExactly $FixedTestGuid
            $Result.ExecutionType                 | Should -BeExactly 'PropertyFunctions'
            $Result.ProjectFilePath.EndsWith($ExpectedFileName) | Should -Be $True
            $Result.ProjectFileHashSHA256         | Should -Not -BeNullOrEmpty
            $Result.ProjectContents               | Should -Match '\$\(\[System.Diagnostics.Process\]::Start\('
            $Result.CustomEnginePath              | Should -BeNullOrEmpty
            $Result.CustomEngineHashSHA256        | Should -BeNullOrEmpty
            $Result.RunnerFilePath                | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath.ToLower()      | Should -BeExactly $ExpectedMSBuildPath.ToLower()
            $Result.RunnerProcessId               | Should -Not -BeNullOrEmpty
            $Result.RunnerCommandLine.EndsWith($ExpectedFileName) | Should -Be $IsCLIPresent
            $Result.RunnerChildProcessId          | Should -Not -BeNullOrEmpty
            $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid

            $Result
        } -TestCases @(
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True  },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True  },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False }
        )

        It 'should execute a custom assembly unregistration method (UseAlternateMSBuildPath: <UseAlternateMSBuildPath>, UseAlternateProjectFilename: <UseAlternateProjectFilename>, OmitProjectFromCLI: <OmitProjectFromCLI>, UseCustomEngineDllPath: <UseCustomEngineDllPath>)' -Tag 'Technique', 'T1127.001' {
            $Arguments = @{}

            if ($UseAlternateProjectFilename) {
                $ExpectedFileName = $AlternateProjectFileName

                $Arguments['ProjectFilePath'] = $ExpectedFileName
            } else {
                $ExpectedFileName = 'test.proj'
            }

            if ($UseAlternateMSBuildPath) {
                $ExpectedMSBuildFileName = $AlternateMSBuildPath.Split('\')[-1]
                $ExpectedMSBuildPath = $AlternateMSBuildPath

                $Arguments['MSBuildFilePath'] = $AlternateMSBuildPath
            } else {
                $ExpectedMSBuildFileName = 'MSBuild.exe'
                $ExpectedMSBuildPath = $DefaultMSBuildPath
            }

            if ($OmitProjectFromCLI) {
                $Arguments['NoCLIProjectFile'] = $True

                $IsCLIPresent = $False
            } else {
                $IsCLIPresent = $True
            }

            if ($UseCustomEngineDllPath) {
                $ExpectedEngineName = 'CustomEngine.txt'

                $Arguments['CustomEngineDllPath'] = $ExpectedEngineName
            } else {
                $ExpectedEngineName = 'CustomEngine.dll'
            }
            
            $Result = Invoke-ATHMSBuild -UseUnregisterAssemblyTask -TestGuid $FixedTestGuid @Arguments
            
            $Result | Should -Not -BeNullOrEmpty

            $Result.TechniqueID                   | Should -BeExactly $ExpectedTechniqueID
            $Result.TestSuccess                   | Should -BeTrue
            $Result.TestGuid                      | Should -BeExactly $FixedTestGuid
            $Result.ExecutionType                 | Should -BeExactly 'CustomUnregisterFunction'
            $Result.ProjectFilePath.EndsWith($ExpectedFileName) | Should -Be $True
            $Result.ProjectFileHashSHA256         | Should -Not -BeNullOrEmpty
            $Result.ProjectContents               | Should -Match 'UnregisterAssembly Assemblies'
            $Result.CustomEnginePath.EndsWith($ExpectedEngineName) | Should -Be $True
            $Result.CustomEngineHashSHA256        | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath                | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath.ToLower()      | Should -BeExactly $ExpectedMSBuildPath.ToLower()
            $Result.RunnerProcessId               | Should -Not -BeNullOrEmpty
            $Result.RunnerCommandLine.EndsWith($ExpectedFileName) | Should -Be $IsCLIPresent
            $Result.RunnerChildProcessId          | Should -Not -BeNullOrEmpty
            $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid

            $Result
        } -TestCases @(
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True }
        )

        It 'should execute a custom logger assembly (UseAlternateMSBuildPath: <UseAlternateMSBuildPath>, UseAlternateProjectFilename: <UseAlternateProjectFilename>, OmitProjectFromCLI: <OmitProjectFromCLI>, UseCustomEngineDllPath: <UseCustomEngineDllPath>)' -Tag 'Technique', 'T1127.001' {
            $Arguments = @{}

            if ($UseAlternateProjectFilename) {
                $ExpectedFileName = $AlternateProjectFileName

                $Arguments['ProjectFilePath'] = $ExpectedFileName
            } else {
                $ExpectedFileName = 'test.proj'
            }

            if ($UseAlternateMSBuildPath) {
                $ExpectedMSBuildFileName = $AlternateMSBuildPath.Split('\')[-1]
                $ExpectedMSBuildPath = $AlternateMSBuildPath

                $Arguments['MSBuildFilePath'] = $AlternateMSBuildPath
            } else {
                $ExpectedMSBuildFileName = 'MSBuild.exe'
                $ExpectedMSBuildPath = $DefaultMSBuildPath
            }

            if ($OmitProjectFromCLI) {
                $Arguments['NoCLIProjectFile'] = $True

                $IsCLIPresent = $False
            } else {
                $IsCLIPresent = $True
            }

            if ($UseCustomEngineDllPath) {
                $ExpectedEngineName = 'CustomEngine.txt'

                $Arguments['CustomEngineDllPath'] = $ExpectedEngineName
            } else {
                $ExpectedEngineName = 'CustomEngine.dll'
            }
            
            $Result = Invoke-ATHMSBuild -UseCustomLogger -TestGuid $FixedTestGuid @Arguments
            
            $Result | Should -Not -BeNullOrEmpty

            $Result.TechniqueID                   | Should -BeExactly $ExpectedTechniqueID
            $Result.TestSuccess                   | Should -BeTrue
            $Result.TestGuid                      | Should -BeExactly $FixedTestGuid
            $Result.ExecutionType                 | Should -BeExactly 'CustomLogger'
            $Result.ProjectFilePath.EndsWith($ExpectedFileName) | Should -Be $True
            $Result.ProjectFileHashSHA256         | Should -Not -BeNullOrEmpty
            $Result.ProjectContents               | Should -Match 'Message Text'
            $Result.CustomEnginePath.EndsWith($ExpectedEngineName) | Should -Be $True
            $Result.CustomEngineHashSHA256        | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath                | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath.ToLower()      | Should -BeExactly $ExpectedMSBuildPath.ToLower()
            $Result.RunnerProcessId               | Should -Not -BeNullOrEmpty
            $Result.RunnerCommandLine.EndsWith($ExpectedFileName) | Should -Be $IsCLIPresent
            $Result.RunnerCommandLine             | Should -Match 'logger'
            $Result.RunnerChildProcessId          | Should -Not -BeNullOrEmpty
            $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid

            $Result
        } -TestCases @(
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True }
        )

        It 'should execute a custom assembly unregistration method (UseAlternateMSBuildPath: <UseAlternateMSBuildPath>, UseAlternateProjectFilename: <UseAlternateProjectFilename>, OmitProjectFromCLI: <OmitProjectFromCLI>, UseCustomEngineDllPath: <UseCustomEngineDllPath>)' -Tag 'Technique', 'T1127.001' {
            $Arguments = @{}

            if ($UseAlternateProjectFilename) {
                $ExpectedFileName = $AlternateProjectFileName

                $Arguments['ProjectFilePath'] = $ExpectedFileName
            } else {
                $ExpectedFileName = 'test.proj'
            }

            if ($UseAlternateMSBuildPath) {
                $ExpectedMSBuildFileName = $AlternateMSBuildPath.Split('\')[-1]
                $ExpectedMSBuildPath = $AlternateMSBuildPath

                $Arguments['MSBuildFilePath'] = $AlternateMSBuildPath
            } else {
                $ExpectedMSBuildFileName = 'MSBuild.exe'
                $ExpectedMSBuildPath = $DefaultMSBuildPath
            }

            if ($OmitProjectFromCLI) {
                $Arguments['NoCLIProjectFile'] = $True

                $IsCLIPresent = $False
            } else {
                $IsCLIPresent = $True
            }

            if ($UseCustomEngineDllPath) {
                $ExpectedEngineName = 'CustomEngine.txt'

                $Arguments['CustomEngineDllPath'] = $ExpectedEngineName
            } else {
                $ExpectedEngineName = 'CustomEngine.dll'
            }
            
            $Result = Invoke-ATHMSBuild -UseUnregisterAssemblyTask -TestGuid $FixedTestGuid @Arguments
            
            $Result | Should -Not -BeNullOrEmpty

            $Result.TechniqueID                   | Should -BeExactly $ExpectedTechniqueID
            $Result.TestSuccess                   | Should -BeTrue
            $Result.TestGuid                      | Should -BeExactly $FixedTestGuid
            $Result.ExecutionType                 | Should -BeExactly 'CustomUnregisterFunction'
            $Result.ProjectFilePath.EndsWith($ExpectedFileName) | Should -Be $True
            $Result.ProjectFileHashSHA256         | Should -Not -BeNullOrEmpty
            $Result.ProjectContents               | Should -Match 'UnregisterAssembly Assemblies'
            $Result.CustomEnginePath.EndsWith($ExpectedEngineName) | Should -Be $True
            $Result.CustomEngineHashSHA256        | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath                | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath.ToLower()      | Should -BeExactly $ExpectedMSBuildPath.ToLower()
            $Result.RunnerProcessId               | Should -Not -BeNullOrEmpty
            $Result.RunnerCommandLine.EndsWith($ExpectedFileName) | Should -Be $IsCLIPresent
            $Result.RunnerChildProcessId          | Should -Not -BeNullOrEmpty
            $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid

            $Result
        } -TestCases @(
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True }
        )

        It 'should execute a custom task factory assembly (UseAlternateMSBuildPath: <UseAlternateMSBuildPath>, UseAlternateProjectFilename: <UseAlternateProjectFilename>, OmitProjectFromCLI: <OmitProjectFromCLI>, UseCustomEngineDllPath: <UseCustomEngineDllPath>)' -Tag 'Technique', 'T1127.001' {
            $Arguments = @{}

            if ($UseAlternateProjectFilename) {
                $ExpectedFileName = $AlternateProjectFileName

                $Arguments['ProjectFilePath'] = $ExpectedFileName
            } else {
                $ExpectedFileName = 'test.proj'
            }

            if ($UseAlternateMSBuildPath) {
                $ExpectedMSBuildFileName = $AlternateMSBuildPath.Split('\')[-1]
                $ExpectedMSBuildPath = $AlternateMSBuildPath

                $Arguments['MSBuildFilePath'] = $AlternateMSBuildPath
            } else {
                $ExpectedMSBuildFileName = 'MSBuild.exe'
                $ExpectedMSBuildPath = $DefaultMSBuildPath
            }

            if ($OmitProjectFromCLI) {
                $Arguments['NoCLIProjectFile'] = $True

                $IsCLIPresent = $False
            } else {
                $IsCLIPresent = $True
            }

            if ($UseCustomEngineDllPath) {
                $ExpectedEngineName = 'CustomEngine.txt'

                $Arguments['CustomEngineDllPath'] = $ExpectedEngineName
            } else {
                $ExpectedEngineName = 'CustomEngine.dll'
            }
            
            $Result = Invoke-ATHMSBuild -UseCustomTaskFactory -TestGuid $FixedTestGuid @Arguments
            
            $Result | Should -Not -BeNullOrEmpty

            $Result.TechniqueID                   | Should -BeExactly $ExpectedTechniqueID
            $Result.TestSuccess                   | Should -BeTrue
            $Result.TestGuid                      | Should -BeExactly $FixedTestGuid
            $Result.ExecutionType                 | Should -BeExactly 'CustomTaskFactory'
            $Result.ProjectFilePath.EndsWith($ExpectedFileName) | Should -Be $True
            $Result.ProjectFileHashSHA256         | Should -Not -BeNullOrEmpty
            $Result.ProjectContents               | Should -Match "<Task>$FixedTestGuid</Task>"
            $Result.CustomEnginePath.EndsWith($ExpectedEngineName) | Should -Be $True
            $Result.CustomEngineHashSHA256        | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath                | Should -Not -BeNullOrEmpty
            $Result.RunnerFilePath.ToLower()      | Should -BeExactly $ExpectedMSBuildPath.ToLower()
            $Result.RunnerProcessId               | Should -Not -BeNullOrEmpty
            $Result.RunnerCommandLine.EndsWith($ExpectedFileName) | Should -Be $IsCLIPresent
            $Result.RunnerChildProcessId          | Should -Not -BeNullOrEmpty
            $Result.RunnerChildProcessCommandLine | Should -Match $FixedTestGuid

            $Result
        } -TestCases @(
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $False },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $False; UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $False; OmitProjectFromCLI = $True ; UseCustomEngineDllPath = $True },
            @{ UseAlternateMSBuildPath = $True;  UseAlternateProjectFilename = $True;  OmitProjectFromCLI = $False; UseCustomEngineDllPath = $True }
        )
    }
}