Invoke-WDACSimulation.psm1

#Requires -RunAsAdministrator
function Invoke-WDACSimulation {
    [CmdletBinding(
        PositionalBinding = $false,
        SupportsShouldProcess = $true
    )]
    Param(
        [ValidateScript({ Test-Path $_ -PathType 'Container' }, ErrorMessage = 'The path you selected is not a folder path.')] 
        [Parameter(Mandatory = $true)][System.String]$FolderPath,

        [ValidateScript({ Test-Path $_ -PathType 'Leaf' }, ErrorMessage = 'The path you selected is not a file path.')]
        [Parameter(Mandatory = $true)][System.String]$XmlFilePath,

        [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck # Used by the entire Cmdlet
    )

    begin {    

        # Importing resources such as functions by dot-sourcing so that they will run in the same scope and their variables will be usable
        . "$psscriptroot\Resources2.ps1"
        . "$psscriptroot\Resources.ps1"

        # Detecting if Debug switch is used, will do debugging actions based on that
        $Debug = $PSBoundParameters.Debug.IsPresent

        # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise
        $ErrorActionPreference = 'Stop'
        if (-NOT $SkipVersionCheck) { . Update-self }
    }
    
    process {
        # For Testing purposes
        # $FolderPath = ''
        # $XmlFilePath = ''
      
        if ($FolderPath) {
            # Store the processed results of the valid Signed files
            [System.Object[]]$SignedResult = @()

            # File paths of the files allowed by Signer/certificate
            [System.Object[]]$AllowedSignedFilePaths = @()

            # File paths of the files allowed by Hash
            [System.Object[]]$AllowedUnsignedFilePaths = @()

            # Stores the final object of all of the results
            [System.Object[]]$MegaOutputObject = @()

            # File paths of the Signed files with HashMismatch Status
            [System.Object[]]$SignedHashMismatchFilePaths = @()

            # File paths of the Signed files with a status that doesn't fall into any other category
            [System.Object[]]$SignedButUnknownFilePaths = @()

            # Hash Sha256 values of all the file rules based on hash in the supplied xml policy file
            [System.Object[]]$SHA256HashesFromXML = (Get-FileRuleOutput -xmlPath $XmlFilePath).hashvalue
                        
            # Get all of the files that WDAC supports from the user provided directory
            [System.Object[]]$CollectedFiles = (Get-ChildItem -Recurse -Path $FolderPath -File -Include '*.sys', '*.exe', '*.com', '*.dll', '*.ocx', '*.msp', '*.mst', '*.msi', '*.js', '*.vbs', '*.ps1', '*.appx').FullName
                     
            # Loop through each file
            $CollectedFiles | ForEach-Object {

                $CurrentFilePath = $_

                # Check see if the file's hash exists in the XML file regardless of whether it's signed or not
                # This is because WDAC policies sometimes have hash rules for signed files too
                try {
                    $CurrentFilePathHash = (Get-AppLockerFileInformation -Path $CurrentFilePath -ErrorAction Stop).hash -replace 'SHA256 0x', ''
                }
                catch {  
                    Write-Debug -Message "Get-AppLockerFileInformation failed for the file at $CurrentFilePath, using New-CIPolicyRule cmdlet..."                 
                    
                    $CurrentHashOutput = New-CIPolicyRule -Level hash -Fallback none -AllowFileNameFallbacks -UserWriteablePaths -DriverFilePath $CurrentFilePath
                  
                    $CurrentFilePathHash = ($CurrentHashOutput | Where-Object { $_.name -like '*Hash Sha256*' }).attributes.hash
                }
           
                # if the file's hash exists in the XML file
                if ($CurrentFilePathHash -in $SHA256HashesFromXML) {
                    $AllowedUnsignedFilePaths += $CurrentFilePath
                }
                else {                                 
                    
                    switch ((Get-AuthenticodeSignature -FilePath $CurrentFilePath).Status) {
                        # If the file is signed and valid
                        'valid' {  
                            # If debug is used show extra info on the console
                            if ($Debug) {                        
                                Write-Host "Currently processing signed file: `n$CurrentFilePath" -ForegroundColor Yellow
                            }
                            # Use the function in Resources2.ps1 file to process it
                            $SignedResult += Compare-SignerAndCertificate -XmlFilePath $XmlFilePath -SignedFilePath $CurrentFilePath | Where-Object { ($_.CertRootMatch -eq $true) -and ($_.CertNameMatch -eq $true) -and ($_.CertPublisherMatch -eq $true) }
                            break
                        }
                        'HashMismatch' {                  
                            $SignedHashMismatchFilePaths += $CurrentFilePath
                            break 
                        } 
                        default { $SignedButUnknownFilePaths += $CurrentFilePath; break }
                    }                  
                }              
            }
            
            # File paths of the files allowed by Signer/certificate, Unique
            [System.Object[]]$AllowedSignedFilePaths = $SignedResult.FilePath | Get-Unique            

       
            if ($AllowedUnsignedFilePaths) {
                # Loop through the first array and create output objects with the file path and source
                foreach ($Path in $AllowedUnsignedFilePaths) {
                    # Create a hash table with the file path and source
                    [System.Collections.Hashtable]$Object = @{
                        FilePath   = $Path
                        Source     = 'Hash'
                        Permission = 'Allowed'
                    }
                    # Convert the hash table to a PSObject and add it to the output array
                    $MegaOutputObject += New-Object -TypeName PSObject -Property $Object
                }  
            }          

            # For valid Signed files
            if ($AllowedSignedFilePaths) {
                # Loop through the second array and create output objects with the file path and source
                foreach ($Path in $AllowedSignedFilePaths) {
                    # Create a hash table with the file path and source properties
                    [System.Collections.Hashtable]$Object = @{
                        FilePath   = $Path
                        Source     = 'Signer'
                        Permission = 'Allowed'
                    }
                    # Convert the hash table to a PSObject and add it to the output array
                    $MegaOutputObject += New-Object -TypeName PSObject -Property $Object
                }            
            }

            # For Signed files with mismatch signature status
            if ($SignedHashMismatchFilePaths) {
                # Loop through the second array and create output objects with the file path and source
                foreach ($Path in $SignedHashMismatchFilePaths) {
                    # Create a hash table with the file path and source properties
                    [System.Collections.Hashtable]$Object = @{
                        FilePath   = $Path
                        Source     = 'Signer'
                        Permission = 'Not Allowed - Hash Mismatch'
                    }
                    # Convert the hash table to a PSObject and add it to the output array
                    $MegaOutputObject += New-Object -TypeName PSObject -Property $Object
                }            
            }

            # For Signed files with Unknown signature status
            if ($SignedButUnknownFilePaths) {
                # Loop through the second array and create output objects with the file path and source
                foreach ($Path in $SignedButUnknownFilePaths) {
                    # Create a hash table with the file path and source properties
                    [System.Collections.Hashtable]$Object = @{
                        FilePath   = $Path
                        Source     = 'Signer'
                        Permission = 'Not Allowed - Expired or unknown'
                    }
                    # Convert the hash table to a PSObject and add it to the output array
                    $MegaOutputObject += New-Object -TypeName PSObject -Property $Object
                }            
            }

            # Unique number of files allowed by hash - used for counting only
            $UniqueFilesAllowedByHash = $MegaOutputObject | Select-Object -Property FilePath, source, Permission -Unique | Where-Object { $_.source -eq 'hash' }

            # To detect files that are not allowed

            # Check if any supported files were found in the user provided directory and any of them were allowed
            if ($($MegaOutputObject.Filepath) -and $CollectedFiles) {
                # Compare the paths of all the supported files that were found in user provided directory with the array of files that were allowed by Signer or hash in the policy
                # Then save the output to a different array
                [System.Object[]]$FinalComparisonForFilesNotAllowed = Compare-Object -ReferenceObject $($MegaOutputObject.Filepath) -DifferenceObject $CollectedFiles -PassThru | Where-Object { $_.SideIndicator -eq '=>' }
            }

            # If there is any files in the user selected directory that is not allowed by the policy
            if ($FinalComparisonForFilesNotAllowed) {

                foreach ($Path in $FinalComparisonForFilesNotAllowed) {
                    # Create a hash table with the file path and source properties
                    [System.Collections.Hashtable]$Object = @{
                        FilePath   = $Path
                        Source     = 'N/A'
                        Permission = 'Not Allowed'
                    }
                    # Convert the hash table to a PSObject and add it to the output array
                    $MegaOutputObject += New-Object -TypeName PSObject -Property $Object
                }  
            }
          
            # Change the color of the Table header
            $PSStyle.Formatting.TableHeader = "$($PSStyle.Foreground.FromRGB(255,165,0))"

            # Display the final main output array as a table - allowed files
            $MegaOutputObject | Select-Object -Property FilePath,
            
            @{
                Label      = 'Source'
                Expression =
                { switch ($_.source) {
                        { $_ -eq 'Signer' } { $color = "$($PSStyle.Foreground.FromRGB(152,255,152))" } # Use PSStyle to set the color
                        { $_ -eq 'Hash' } { $color = "$($PSStyle.Foreground.FromRGB(255,255,49))" } # Use PSStyle to set the color
                        { $_ -eq 'N/A' } { $color = "$($PSStyle.Foreground.FromRGB(255,20,147))" } # Use PSStyle to set the color
                    }
                    "$color$($_.source)$($PSStyle.Reset)" # Use PSStyle to reset the color
                }
            }, Permission -Unique | Sort-Object -Property Permission | Format-Table -Property FilePath, Source, Permission
            
            # Showing Signature based allowed file details
            &$WriteLavender "`n$($AllowedSignedFilePaths.count) File(s) Inside the Selected Folder Are Allowed by Signatures by Your Policy."
            
            # Showing Hash based allowed file details
            &$WriteLavender "$($UniqueFilesAllowedByHash.count) File(s) Inside the Selected Folder Are Allowed by Hashes by Your Policy.`n"
                        
            # Export the output as CSV
            $MegaOutputObject | Select-Object -Property FilePath, source, Permission -Unique | Sort-Object -Property Permission | Export-Csv -Path .\WDACSimulationOutput.csv -Force

            if ($Debug) {
                Write-Host 'Files that were UNSIGNED' -ForegroundColor Blue
                $AllowedUnsignedFilePaths
            }        
           
        }        
    }
    
    <#
.SYNOPSIS
Simulates the deployment of the WDAC policy
 
.LINK
https://github.com/HotCakeX/Harden-Windows-Security/wiki/Invoke-WDACSimulation
 
.DESCRIPTION
Simulates the deployment of the WDAC policy by analyzing a folder and checking which of the files in the folder are allowed by a user selected policy xml file
 
.COMPONENT
Windows Defender Application Control, ConfigCI PowerShell module
 
.FUNCTIONALITY
Simulates the deployment of the WDAC policy
 
.PARAMETER FolderPath
Provide path to a folder where you want WDAC simulation to take place
 
.PARAMETER XmlFilePath
Provide path to a policy xml file that you want the cmdlet to simulate its deployment and running files against it
 
.PARAMETER SkipVersionCheck
Can be used with any parameter to bypass the online version check - only to be used in rare cases
 
#>

}

# Importing argument completer ScriptBlocks
. "$psscriptroot\ArgumentCompleters.ps1"
# Set PSReadline tab completion to complete menu for easier access to available parameters - Only for the current session
Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete
Register-ArgumentCompleter -CommandName 'Invoke-WDACSimulation' -ParameterName 'FolderPath' -ScriptBlock $ArgumentCompleterFolderPathsPicker
Register-ArgumentCompleter -CommandName 'Invoke-WDACSimulation' -ParameterName 'XmlFilePath' -ScriptBlock $ArgumentCompleterXmlFilePathsPicker