Add Dependencies section to test Yaml and support to use them in the PS execution framework (#772)

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* first draft at dependencies

* lowercase url
This commit is contained in:
Carrie Roberts
2020-01-09 07:36:08 -07:00
committed by Michael Haag
parent 550ba03c22
commit 511bb87af2
202 changed files with 5272 additions and 5691 deletions
@@ -70,8 +70,7 @@
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(
'Invoke-AtomicTest',
'Write-ExeutionLog'
'Invoke-AtomicTest'
)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
@@ -0,0 +1,5 @@
function Get-PrereqExecutor ($test) {
if ($nul -eq $test.dependency_executor_name) { $executor = $test.executor.name }
else { $executor = $test.dependency_executor_name }
$executor
}
@@ -0,0 +1,16 @@
function Invoke-CheckPrereqs ($test, $isElevated, $customInputArgs, $PathToAtomicsFolder) {
$FailureReasons = New-Object System.Collections.ArrayList
if ($test.executor.elevation_required -and -not $isElevated) {
$FailureReasons.add("Elevation required but not provided`n") | Out-Null
}
foreach ($dep in $test.dependencies) {
$executor = Get-PrereqExecutor $test
$final_command = Merge-InputArgs $dep.prereq_command $test $customInputArgs $PathToAtomicsFolder
$success = Invoke-ExecuteCommand $final_command $executor
$description = Merge-InputArgs $dep.description $test $customInputArgs $PathToAtomicsFolder
if (-not $success) {
$FailureReasons.add($description) | Out-Null
}
}
$FailureReasons
}
@@ -0,0 +1,32 @@
function Invoke-ExecuteCommand ($finalCommand, $executor) {
$null = @(
$success = $true
Write-Verbose -Message 'Invoking Atomic Tests using defined executor'
$finalCommandEscaped = $finalCommand -replace "`"", "```""
if ($executor -eq "command_prompt" -or $executor -eq "sh" -or $executor -eq "bash") {
$execCommand = $finalCommandEscaped.Split("`n") | Where-Object { $_ -ne "" }
$exitCodes = New-Object System.Collections.ArrayList
$execPrefix = "cmd.exe /c"
if ($executor -eq "sh") { $execPrefix = "sh -c" }
if ($executor -eq "bash") { $execPrefix = "bash -c" }
$execCommand | ForEach-Object {
$retval = Invoke-Expression "$execPrefix `"$_`" "
if ($nul -ne $retval) { $retval | Write-Host }
$exitCodes.Add($LASTEXITCODE) | Out-Null
}
$nonZeroExitCodes = $exitCodes | Where-Object { $_ -ne 0 }
$success = $nonZeroExitCodes.Count -eq 0
}
elseif ($executor -eq "powershell") {
$finalCommand = $finalCommand.trim()
$execCommand = "Invoke-Command -ScriptBlock {$finalCommand}"
$res = Invoke-Expression $execCommand
$success = [string]::IsNullOrEmpty($finalCommand) -or $res -eq 0
}
else {
Write-Warning -Message "Unable to generate or execute the command line properly. Unknown executor"
$success = $false
}
)
$success
}
@@ -0,0 +1,31 @@
function Get-InputArgs([hashtable]$ip, $customInputArgs, $PathToAtomicsFolder) {
$defaultArgs = @{ }
foreach ($key in $ip.Keys) {
$defaultArgs[$key] = $ip[$key].default
}
# overwrite defaults with any user supplied values
foreach ($key in $customInputArgs.Keys) {
if ($defaultArgs.Keys -contains $key) {
# replace default with user supplied
$defaultArgs.set_Item($key, $customInputArgs[$key])
}
}
# Replace $PathToAtomicsFolder or PathToAtomicsFolder with the actual -PathToAtomicsFolder value
foreach ($key in $defaultArgs.Clone().Keys) {
$defaultArgs.set_Item($key, ($defaultArgs[$key] -replace "\`$PathToAtomicsFolder", $PathToAtomicsFolder -replace "PathToAtomicsFolder", $PathToAtomicsFolder))
}
$defaultArgs
}
function Merge-InputArgs($finalCommand, $test, $customInputArgs, $PathToAtomicsFolder) {
if (($null -ne $finalCommand) -and ($test.input_arguments.Count -gt 0)) {
Write-Verbose -Message 'Replacing inputArgs with user specified values, or default values if none provided'
$inputArgs = Get-InputArgs $test.input_arguments $customInputArgs $PathToAtomicsFolder
foreach ($key in $inputArgs.Keys) {
$findValue = '#{' + $key + '}'
$finalCommand = $finalCommand.Replace($findValue, $inputArgs[$key])
}
}
$finalCommand
}
@@ -0,0 +1,45 @@
function Show-Details ($test, $testCount, $technique, $customInputArgs, $PathToAtomicsFolder) {
# Header info
$tName = $technique.display_name.ToString() + " " + $technique.attack_technique.ToString()
Write-KeyValue "[********BEGIN TEST*******]`nTechnique: " $tName
Write-KeyValue "Atomic Test Name: " $test.name.ToString()
Write-KeyValue "Atomic Test Number: " $testCount
Write-KeyValue "Description: " $test.description.ToString().trim()
# Attack Commands
Write-Host -ForegroundColor Yellow "Attack Commands:"
$elevationRequired = $false
if ($nul -ne $test.executor.elevation_required ) { $elevationRequired = $test.executor.elevation_required }
$executor_name = $test.executor.name
Write-KeyValue "Executor: " $executor_name
Write-KeyValue "ElevationRequired: " $elevationRequired
$final_command = Merge-InputArgs $test.executor.command $test $customInputArgs $PathToAtomicsFolder
Write-KeyValue "Command:`n" $test.executor.command.trim()
if ($test.executor.command -ne $final_command) { Write-KeyValue "Command (with inputs):`n" $final_command.trim() }
# Cleanup Commands
if ($nul -ne $test.executor.cleanup_command) {
Write-Host -ForegroundColor Yellow "Cleanup Commands:"
$final_command = Merge-InputArgs $test.executor.cleanup_command $test $customInputArgs $PathToAtomicsFolder
Write-KeyValue "Command:`n" $test.executor.cleanup_command.trim()
if ($test.executor.command -ne $final_command) { Write-KeyValue "Command (with inputs):`n" $final_command.trim() }
}
# Dependencies
if ($nul -ne $test.dependencies) {
Write-Host -ForegroundColor Yellow "Dependencies:"
foreach ($dep in $test.dependencies) {
$final_command_prereq = Merge-InputArgs $dep.prereq_command $test $customInputArgs $PathToAtomicsFolder
$final_command_get_prereq = Merge-InputArgs $dep.get_prereq_command $test $customInputArgs $PathToAtomicsFolder
$description = Merge-InputArgs $dep.description $test $customInputArgs $PathToAtomicsFolder
Write-KeyValue "Description: " $description.trim()
Write-KeyValue "Check Prereq Command:`n" $dep.prereq_command.trim()
if ( $dep.prereq_command -ne $final_command_prereq ) { Write-KeyValue "Check Prereq Command (with inputs):`n" $final_command_prereq.trim() }
Write-KeyValue "Get Prereq Command:`n" $dep.get_prereq_command.trim()
if ( $dep.get_prereq_command -ne $final_command_get_prereq ) { Write-KeyValue "Get Prereq Command (with inputs):`n" $final_command_get_prereq.trim() }
}
}
# Footer
Write-Host -Fore Cyan "[!!!!!!!!END TEST!!!!!!!]`n`n"
}
@@ -0,0 +1,4 @@
function Write-KeyValue ($key, $value) {
Write-Host -ForegroundColor Cyan -NoNewline $key
Write-Host -ForegroundColor Green $value
}
@@ -0,0 +1,14 @@
function Write-PrereqResults ($FailureReasons, $testId) {
if ($FailureReasons.Count -eq 0) {
Write-KeyValue "Prerequisites met: " $testId
}
else {
Write-Host -ForegroundColor Red "Prerequisites not met: $testId"
foreach ($reason in $FailureReasons) {
Write-Host -ForegroundColor Yellow -NoNewline "`t[*] $reason"
}
Write-Host -ForegroundColor Yellow -NoNewline "`nTry installing prereq's with the "
Write-Host -ForegroundColor Cyan -NoNewline "-GetPrereqs"
Write-Host -ForegroundColor Yellow " switch"
}
}
@@ -59,6 +59,12 @@ function Invoke-AtomicTest {
[switch]
$CheckPrereqs = $false,
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = 'technique')]
[switch]
$GetPrereqs = $false,
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = 'technique')]
@@ -88,7 +94,7 @@ function Invoke-AtomicTest {
BEGIN { } # Intentionally left blank and can be removed
PROCESS {
Write-Verbose -Message 'Attempting to run Atomic Techniques'
if ($ShowDetails -or $InformationPreference -eq "Continue") { $info = $true } else { $info = $false }
Write-Host -ForegroundColor Cyan "PathToAtomicsFolder = $PathToAtomicsFolder`n"
$isElevated = $false
$targetPlatform = "linux"
@@ -101,45 +107,7 @@ function Invoke-AtomicTest {
$targetPlatform = "windows"
$isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
Write-Host -ForegroundColor Cyan "PathToAtomicsFolder = $PathToAtomicsFolder`n"
function Get-InputArgs([hashtable]$ip) {
$defaultArgs = @{ }
foreach ($key in $ip.Keys) {
$defaultArgs[$key] = $ip[$key].default
}
# overwrite defaults with any user supplied values
foreach ($key in $InputArgs.Keys) {
if ($defaultArgs.Keys -contains $key) {
# replace default with user supplied
$defaultArgs.set_Item($key, $InputArgs[$key])
}
}
# Replace $PathToAtomicsFolder or PathToAtomicsFolder with the actual -PathToAtomicsFolder value
foreach ($key in $defaultArgs.Clone().Keys) {
$defaultArgs.set_Item($key, ($defaultArgs[$key] -replace "\`$PathToAtomicsFolder", $PathToAtomicsFolder -replace "PathToAtomicsFolder", $PathToAtomicsFolder))
}
$defaultArgs
}
function Write-PrereqResults ($success) {
if ($CheckPrereqs ) {
if ($test.executor.elevation_required -and -not $isElevated) {
Write-Host -ForegroundColor Red "Prerequisites not met: $testId (elevation required but not provided)"
}
elseif ($success) {
Write-Host -ForegroundColor Green "Prerequisites met: $testId"
}
else {
Write-Host -ForegroundColor Red "Prerequisites not met: $testId"
}
}
elseif ($test.executor.elevation_required -and -not $isElevated) {
Write-Host -ForegroundColor yellow "Warning: Test '$testId' should be run from an elevated context but wasn't. Try running this test with administrative privileges. "
}
}
function Invoke-AtomicTestSingle ($AT) {
$AT = $AT.ToUpper()
@@ -198,85 +166,64 @@ function Invoke-AtomicTest {
continue
}
if ($info) {
Write-Host -Fore Blue ("[********BEGIN TEST*******]`nTechnique: " +
$technique.display_name.ToString(), $technique.attack_technique.ToString())
Write-Host -Fore Blue "Atomic Test Name: " $test.name.ToString()
Write-Host -Fore Blue "Atomic Test Number: " $testCount
Write-Host -Fore Blue "Description: " $test.description.ToString().trim()
if ($ShowDetails) {
Show-Details $test $testCount $technique $InputArgs $PathToAtomicsFolder
continue
}
Write-Debug -Message 'Gathering final Atomic test command'
$testId = "$AT-$testCount $($test.name)"
$prereqCommand = $test.executor.prereq_command
$command = $test.executor.command
$cleanupCommand = $test.executor.cleanup_command
if ($CheckPrereqs) {
$finalCommand = $prereqCommand
}
elseif ($Cleanup) {
$finalCommand = $cleanupCommand
}
else {
$finalCommand = $command
Write-KeyValue "CheckPrereq's for: " $testId
$failureReasons = Invoke-CheckPrereqs $test $isElevated $InputArgs $PathToAtomicsFolder
Write-PrereqResults $FailureReasons $testId
}
elseif ($GetPrereqs) {
Write-KeyValue "GetPrereq's for: " $testId
if ($nul -eq $test.dependencies) { Write-KeyValue "No Preqs Defined"; continue}
foreach ($dep in $test.dependencies) {
$executor = Get-PrereqExecutor $test
$description = (Merge-InputArgs $dep.description $test $InputArgs $PathToAtomicsFolder).trim()
Write-KeyValue "Attempting to satisfy prereq: " $description
$final_command_prereq = Merge-InputArgs $dep.prereq_command $test $InputArgs $PathToAtomicsFolder
$final_command_get_prereq = Merge-InputArgs $dep.get_prereq_command $test $InputArgs $PathToAtomicsFolder
$success = Invoke-ExecuteCommand $final_command_prereq $executor
if ($success) {
Write-KeyValue "Prereq already met: " $description
}
else {
if (($null -ne $finalCommand) -and ($test.input_arguments.Count -gt 0)) {
Write-Verbose -Message 'Replacing inputArgs with user specified values, or default values if none provided'
$inputArgs = Get-InputArgs $test.input_arguments
foreach ($key in $inputArgs.Keys) {
$findValue = '#{' + $key + '}'
$finalCommand = $finalCommand.Replace($findValue, $inputArgs[$key])
$retval = Invoke-ExecuteCommand $final_command_get_prereq $executor
$success = Invoke-ExecuteCommand $final_command_prereq $executor
if ($success) {
Write-KeyValue "Prereq successfully met: " $description
}
else {
Write-Host -ForegroundColor Red "Failed to meet prereq: $description"
}
}
}
if ($test.executor.elevation_required -and -not $isElevated) {
Write-Host -ForegroundColor Red "Elevation required but not provided"
}
}
Write-Debug -Message 'Getting executor and build command script'
if ($ShowDetails -and ($null -ne $finalCommand)) {
$executor_name = $test.executor.name
Write-Host -Fore Blue "Executor: $executor_name"
Write-Host -Fore Blue "ElevationRequired: $($($test.executor).elevation_required)`nCommands:`n"
Write-Host -Fore cyan $finalCommand
elseif ($Cleanup) {
Write-KeyValue "Executing Cleanup for Test: " $testId
$final_command = Merge-InputArgs $test.executor.cleanup_command $test $InputArgs $PathToAtomicsFolder
Invoke-ExecuteCommand $final_command $test.executor.name | Out-Null
Write-KeyValue "Done"
}
else {
Write-KeyValue "Executing Test: " $testId
$startTime = get-date
Write-Verbose -Message 'Invoking Atomic Tests using defined executor'
$testName = $test.name.ToString()
if ($pscmdlet.ShouldProcess($testName, 'Execute Atomic Test')) {
$testId = "$AT-$testCount $testName"
$attackExecuted = $false
$executor = $test.executor.name
$finalCommandEscaped = $finalCommand -replace "`"", "```""
if ($executor -eq "command_prompt" -or $executor -eq "sh" -or $executor -eq "bash") {
$execCommand = $finalCommandEscaped.Split("`n") | Where-Object { $_ -ne "" }
$exitCodes = New-Object System.Collections.ArrayList
$execPrefix = "cmd.exe /c"
if ($executor -eq "sh") { $execPrefix = "sh -c" }
if ($executor -eq "bash") { $execPrefix = "bash -c" }
$execCommand | ForEach-Object {
Invoke-Expression "$execPrefix `"$_`" "
$exitCodes.Add($LASTEXITCODE) | Out-Null
}
$nonZeroExitCodes = $exitCodes | Where-Object { $_ -ne 0 }
$success = $nonZeroExitCodes.Count -eq 0
}
elseif ($executor -eq "powershell") {
$execCommand = "Invoke-Command -ScriptBlock {$finalCommand}"
$res = Invoke-Expression $execCommand
$success = [string]::IsNullOrEmpty($finalCommand) -or $res -eq 0
}
else {
Write-Warning -Message "Unable to generate or execute the command line properly."
continue
}
if (!$CheckPrereqs -and !$Cleanup) { $attackExecuted = $true }
Write-PrereqResults ($success) $testId
if (-not $NoExecutionLog -and $attackExecuted) { Write-ExecutionLog $startTime $AT $testCount $testName $ExecutionLogPath }
} # End of if ShouldProcess block
} # End of else statement
if ($info) { Write-Host -Fore Blue "[!!!!!!!!END TEST!!!!!!!]`n`n" }
$final_command = Merge-InputArgs $test.executor.command $test $InputArgs $PathToAtomicsFolder
Invoke-ExecuteCommand $final_command $test.executor.name | Out-Null
Write-ExecutionLog $startTime $AT $testCount $testName $ExecutionLogPath
Write-KeyValue "Done"
}
} # End of foreach Test in single Atomic Technique
} # End of foreach Technique in Atomic Tests
} # End of Invoke-AtomicTestSingle function
@@ -291,7 +238,7 @@ function Invoke-AtomicTest {
$AllAtomicTests.GetEnumerator() | Foreach-Object { Invoke-AtomicTestSingle $_ }
}
if ( ($Force -or $CheckPrereqs) -or $psCmdlet.ShouldContinue( 'Do you wish to execute all tests?',
if ( ($Force -or $CheckPrereqs -or $ShowDetails -or $GetPrereqs) -or $psCmdlet.ShouldContinue( 'Do you wish to execute all tests?',
"Highway to the danger zone, Executing All Atomic Tests!" ) ) {
Invoke-AllTests
}
@@ -0,0 +1,21 @@
function Invoke-WebRequestVerifyHash ($url, $outfile, $hash) {
$success = $false
$null = @(
New-Item -ItemType Directory (Split-Path $outfile) -Force | Out-Null
$ms = New-Object IO.MemoryStream
(New-Object System.Net.WebClient).OpenRead($url).copyto($ms)
$ms.seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null
$actualHash = (Get-FileHash -InputStream $ms).Hash
if ( $hash -eq $actualHash) {
$ms.seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null
$fileStream = New-Object IO.FileStream $outfile, ([System.IO.FileMode]::Create)
$ms.CopyTo($fileStream);
$fileStream.Close()
$success = $true
}
else {
Write-Host -ForegroundColor red "File hash mismatch, expected: $hash, actual: $actualHash"
}
)
$success
}
@@ -48,6 +48,9 @@ Force
### Manual Installation
`set-executionpolicy Unrestricted`
[PowerShell-Yaml](https://github.com/cloudbase/powershell-yaml) is required to parse Atomic yaml files:
`Install-Module -Name powershell-yaml -Scope CurrentUser`
@@ -96,24 +99,10 @@ Invoke-AtomicTest All -PathToAtomicsFolder C:\AtomicRedTeam\atomics
#### Display Test Details without Executing the Test
Show the attack commands:
```powershell
Invoke-AtomicTest All -ShowDetails
```
Show the Prereq commands:
```powershell
Invoke-AtomicTest All -CheckPrereqs -ShowDetails
```
Show the Cleanup commands:
```powershell
Invoke-AtomicTest All -Cleanup -ShowDetails
```
Using the `ShowDetails` switch causes the test details to be printed to the screen and allows for easy copy and paste execution.
Note: you may need to change the path where the test definitions are found with the `PathToAtomicsFolder` parameter.
@@ -123,28 +112,6 @@ Note: you may need to change the path where the test definitions are found with
Invoke-AtomicTest T1117
```
By default, test execution details are written to `Invoke-AtomicTest-ExecutionLog.csv` in the current directory.
#### Specify an Alternate Path for the Execution Log
```powershell
Invoke-AtomicTest T1117 -ExecutionLogPath 'C:\Temp\mylog.csv'
```
By default, test execution details are written to `Invoke-AtomicTest-ExecutionLog.csv` in the current directory. Use the `-ExecutionLogPath` parameter to write to a different file. Nothing is logged in the execution log when only running pre-requisite checks with `-CheckPrereqs` or cleanup commands with `-Cleanup`. Use the `-NoExecutionLog` switch to not write execution details to disk.
#### Check that Prerequistes for a Given Technique are met
```powershell
Invoke-AtomicTest T1117 -CheckPrereqs
```
For the "command_prompt", "bash", and "sh" executors, if any of the prereq_command's return a non-zero exit code, the pre-requisites are not met. Example: **fltmc.exe filters | findstr #{sysmon_driver}**
For the "powershell" executor, the prereq_command's are run as a script block and the script must return 0 if the pre-requisites are met. Example: **if(Test-Path C:\Windows\System32\cmd.exe) { 0 } else { -1 }**
Pre-requisites will also be reported as not met if the test is defined with `elevation_required: true` but the current context is not elevated. You can still execute an attack even if the pre-requisites are not met but execution may fail.
#### Execute Specific Attacks (by Attack Number) for a Given Technique
```powershell
@@ -156,6 +123,40 @@ Invoke-AtomicTest T1117 -TestNumbers 1, 2
```powershell
Invoke-AtomicTest T1117 -TestNames "Regsvr32 remote COM scriptlet execution","Regsvr32 local DLL execution"
```
By default, test execution details are written to `Invoke-AtomicTest-ExecutionLog.csv` in the current directory.
#### Specify an Alternate Path for the Execution Log
```powershell
Invoke-AtomicTest T1117 -ExecutionLogPath 'C:\Temp\mylog.csv'
```
By default, test execution details are written to `Invoke-AtomicTest-ExecutionLog.csv` in the current directory. Use the `-ExecutionLogPath` parameter to write to a different file. Execution is only logged in the execution log when the attack commands are run (not when `-ShowDetails` , `-CheckPrereqs`, `GetPrereqs`, or `-Cleanup` swiches are used). Use the `-NoExecutionLog` switch to not write execution details to disk.
#### Check that Prerequistes for a given test are met
```powershell
Invoke-AtomicTest T1117 -TestNumber 1 -CheckPrereqs
```
For the "command_prompt", "bash", and "sh" executors, if any of the prereq_command's return a non-zero exit code, the pre-requisites are not met. Example: **fltmc.exe filters | findstr #{sysmon_driver}**
For the "powershell" executor, the prereq_command's are run as a script block and the script must return 0 if the pre-requisites are met. Example: **if(Test-Path C:\Windows\System32\cmd.exe) { 0 } else { -1 }**
Pre-requisites will also be reported as not met if the test is defined with `elevation_required: true` but the current context is not elevated. You can still execute an attack even if the pre-requisites are not met but execution may fail.
#### Get Prerequistes
```powershell
Invoke-AtomicTest T1117 -TestNumber 1 -GetPrereqs
```
This will run the "Get Prereq Commands" listed in the Dependencies section for the test.
The execution framework provides a helpful PowerShell function called `Invoke-WebRequestVerifyHash` which only downloads and saves a file to disk if the file hash matches the specified value. Call this method by passing in the url of the file to download, the path where it should be saved, and lastly the expected Sha256 file hash.
The function returns `$true` if the file was saved to disk, `$false` otherwise.
#### Specify Input Parameters on the Command Line
```powershell
@@ -173,37 +174,17 @@ Invoke-AtomicTest T1089 -TestNames "Uninstall Sysmon" -Cleanup
## Additional Examples
If you would like output when running tests using the following:
#### Informational Stream
```powershell
Invoke-AtomicTest T1117 -InformationAction Continue
```
#### Verbose Stream
```powershell
Invoke-AtomicTest T1117 -Verbose
```
#### Debug Stream
```powershell
Invoke-AtomicTest T1117 -Debug
```
#### Confirm
To run all tests without confirming them run using the Confirm switch to false
```powershell
Invoke-AtomicTest T1117 -Confirm:$false
Invoke-AtomicTest All -Confirm:$false
```
Or you can set your `$ConfirmPreference` to 'Medium'
```powershell
$ConfirmPreference = 'Medium'
Invoke-AtomicTest T1117
Invoke-AtomicTest All
```