New-DenyWDACConfig.psm1

#Requires -RunAsAdministrator
function New-DenyWDACConfig {
    [CmdletBinding(      
        DefaultParameterSetName = "Drivers",
        PositionalBinding = $false,
        SupportsShouldProcess = $true,
        ConfirmImpact = 'High'
    )]
    Param(
        # Main parameters for position 0
        [Alias("N")]
        [Parameter(Mandatory = $false, ParameterSetName = "Normal")][Switch]$Normal,

        [Alias("D")]
        [Parameter(Mandatory = $false, ParameterSetName = "Drivers")][Switch]$Drivers,

        [ValidatePattern('^[a-zA-Z0-9 ]+$', ErrorMessage = "The Supplemental Policy Name can only contain alphanumeric characters and spaces.")]
        [parameter(Mandatory = $true, ParameterSetName = "Normal", ValueFromPipelineByPropertyName = $true)]
        [parameter(Mandatory = $true, ParameterSetName = "Drivers", ValueFromPipelineByPropertyName = $true)]        
        [System.String]$PolicyName,
   
        [ValidateScript({ Test-Path $_ -PathType 'Container' }, ErrorMessage = "The path you selected is not a folder path.")]            
        [parameter(Mandatory = $true, ParameterSetName = "Normal")]
        [parameter(Mandatory = $true, ParameterSetName = "Drivers")]
        [System.String[]]$ScanLocations,

        [ValidateSet([Levelz])]
        [Parameter(Mandatory = $false, ParameterSetName = "Normal")]
        [Parameter(Mandatory = $false, ParameterSetName = "Drivers")]
        [System.String]$Level = "FilePublisher", # Setting the default value for the Level parameter

        [ValidateSet([Fallbackz])]
        [Parameter(Mandatory = $false, ParameterSetName = "Normal")]
        [Parameter(Mandatory = $false, ParameterSetName = "Drivers")]
        [System.String[]]$Fallbacks = "Hash", # Setting the default value for the Fallbacks parameter

        [Parameter(Mandatory = $false, ParameterSetName = "Normal")]
        [Switch]$AllowFileNameFallbacks,
        
        [ValidateSet("OriginalFileName", "InternalName", "FileDescription", "ProductName", "PackageFamilyName", "FilePath")]
        [Parameter(Mandatory = $false, ParameterSetName = "Normal")]
        [System.String]$SpecificFileNameLevel,

        [Parameter(Mandatory = $false, ParameterSetName = "Normal")]
        [Switch]$NoUserPEs,

        [Parameter(Mandatory = $false, ParameterSetName = "Normal")]
        [Switch]$NoScript,

        [Parameter(Mandatory = $false, ParameterSetName = "Normal")]
        [Parameter(Mandatory = $false, ParameterSetName = "Drivers")]
        [Switch]$Deployit,
        
        [Parameter(Mandatory = $false)][Switch]$SkipVersionCheck
    )

    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\Resources.ps1"

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

        # argument tab auto-completion and ValidateSet for Fallbacks
        Class Fallbackz : System.Management.Automation.IValidateSetValuesGenerator {
            [System.String[]] GetValidValues() {
                $Fallbackz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None')
           
                return [System.String[]]$Fallbackz
            }
        }
        
        # argument tab auto-completion and ValidateSet for level
        Class Levelz : System.Management.Automation.IValidateSetValuesGenerator {
            [System.String[]] GetValidValues() {
                $Levelz = ('Hash', 'FileName', 'SignedVersion', 'Publisher', 'FilePublisher', 'LeafCertificate', 'PcaCertificate', 'RootCertificate', 'WHQL', 'WHQLPublisher', 'WHQLFilePublisher', 'PFN', 'FilePath', 'None')
               
                return [System.String[]]$Levelz
            }
        }

        # Stop operation as soon as there is an error anywhere, unless explicitly specified otherwise
        $ErrorActionPreference = 'Stop'
        if (-NOT $SkipVersionCheck) { . Update-self }       
    }
    
    process {
        # Create deny supplemental policy for general files, apps etc.
        if ($Normal) {
            # remove any possible files from previous runs
            Remove-Item -Path ".\ProgramDir_ScanResults*.xml" -Force -ErrorAction SilentlyContinue
            # An array to hold the temporary xml files of each user-selected folders
            $PolicyXMLFilesArray = @()

            ######################## Process Program Folders From User input #####################
            for ($i = 0; $i -lt $ScanLocations.Count; $i++) {

                # Creating a hash table to dynamically add parameters based on user input and pass them to New-Cipolicy cmdlet
                [System.Collections.Hashtable]$UserInputProgramFoldersPolicyMakerHashTable = @{
                    FilePath             = ".\ProgramDir_ScanResults$($i).xml"
                    ScanPath             = $ScanLocations[$i]
                    Level                = $Level
                    Fallback             = $Fallbacks
                    MultiplePolicyFormat = $true
                    UserWriteablePaths   = $true
                    Deny                 = $true
                }
                # Assess user input parameters and add the required parameters to the hash table
                if ($AllowFileNameFallbacks) { $UserInputProgramFoldersPolicyMakerHashTable['AllowFileNameFallbacks'] = $true }
                if ($SpecificFileNameLevel) { $UserInputProgramFoldersPolicyMakerHashTable['SpecificFileNameLevel'] = $SpecificFileNameLevel }
                if ($NoScript) { $UserInputProgramFoldersPolicyMakerHashTable['NoScript'] = $true }                      
                if (!$NoUserPEs) { $UserInputProgramFoldersPolicyMakerHashTable['UserPEs'] = $true } 

                # Create the supplemental policy via parameter splatting
                New-CIPolicy @UserInputProgramFoldersPolicyMakerHashTable
            }            

            Write-Debug -Message "The Deny policy with the following configuration is being created"
            if ($Debug) { $UserInputProgramFoldersPolicyMakerHashTable }
            
            # Merge-cipolicy accept arrays - collecting all the policy files created by scanning user specified folders
            $ProgramDir_ScanResults = Get-ChildItem ".\" | Where-Object { $_.Name -like 'ProgramDir_ScanResults*.xml' }                
            foreach ($file in $ProgramDir_ScanResults) {
                $PolicyXMLFilesArray += $file.FullName
            
            }

            # Creating an empty policy that only contains 2 allow rules, going to be merged with the Deny only base policy
            New-AllowAllPolicy | Out-File '.\AllowAllPolicy.xml'
            # Adding the AllowAll policy path to the array of policy paths
            $PolicyXMLFilesArray += '.\AllowAllPolicy.xml'
            # creating the final Deny base policy from the xml files in the paths array
            Merge-CIPolicy -PolicyPaths $PolicyXMLFilesArray -OutputFilePath ".\DenyPolicy $PolicyName.xml" | Out-Null
                            
            $policyID = Set-CiPolicyIdInfo -FilePath "DenyPolicy $PolicyName.xml" -ResetPolicyID -PolicyName "$PolicyName"
            $policyID = $policyID.Substring(11)
            Set-CIPolicyVersion -FilePath "DenyPolicy $PolicyName.xml" -Version "1.0.0.0"
            
            @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object {
                Set-RuleOption -FilePath "DenyPolicy $PolicyName.xml" -Option $_ }
                
            @(3, 4, 9, 10, 13, 18) | ForEach-Object {
                Set-RuleOption -FilePath "DenyPolicy $PolicyName.xml" -Option $_ -Delete }        
            
            Set-HVCIOptions -Strict -FilePath "DenyPolicy $PolicyName.xml"        
            ConvertFrom-CIPolicy "DenyPolicy $PolicyName.xml" "$policyID.cip" | Out-Null
            [PSCustomObject]@{
                DenyPolicyFile = "DenyPolicy $PolicyName.xml"
                DenyPolicyGUID = $PolicyID
            }
            
            if (!$Debug) {
                Remove-Item -Path ".\ProgramDir_ScanResults*.xml" -Force
                Remove-Item -Path '.\AllowAllPolicy.xml' -Force
            }
            
            if ($Deployit) {                
                CiTool --update-policy "$policyID.cip" -json
                Remove-Item -Path "$policyID.cip" -Force
                Write-host -NoNewline "`n$policyID.cip for " -ForegroundColor Green
                Write-host -NoNewline "$PolicyName" -ForegroundColor Magenta
                Write-host " has been deployed." -ForegroundColor Green
            }
        }
        # Create Deny base policy for Driver files
        elseif ($Drivers) {           

            powershell.exe {
                $DriverFilesObject = @()
                # loop through each user-selected folder paths
                foreach ($ScanLocation in $args[0]) {
                    # DriverFile object holds the full details of all of the scanned drivers - This scan is greedy, meaning it stores as much information as it can find
                    # about each driver file, any available info about digital signature, hash, FileName, Internal Name etc. of each driver is saved and nothing is left out
                    $DriverFilesObject += Get-SystemDriver -ScanPath $ScanLocation -UserPEs            
                }

                [System.Collections.Hashtable]$PolicyMakerHashTable = @{
                    FilePath             = ".\DenyPolicy Temp.xml"
                    DriverFiles          = $DriverFilesObject
                    Level                = $args[1]
                    Fallback             = $args[2]
                    MultiplePolicyFormat = $true
                    UserWriteablePaths   = $true
                    Deny                 = $true
                }
                # Creating a base policy using the DriverFile object and specifying which detail about each driver should be used in the policy file
                New-CIPolicy @PolicyMakerHashTable
            
            } -args $ScanLocations, $Level, $Fallbacks

            # Creating an empty policy that only contains 2 allow rules, going to be merged with the Deny only base policy
            New-AllowAllPolicy | Out-File '.\AllowAllPolicy.xml'

            # Letting the AllowAll policy be first so that its AllowAll rules will be on top of each node for better visibility
            Merge-CIPolicy -PolicyPaths '.\AllowAllPolicy.xml', ".\DenyPolicy Temp.xml" -OutputFilePath ".\DenyPolicy $PolicyName.xml" | Out-Null

            Remove-Item -Path ".\DenyPolicy Temp.xml" -Force
            Remove-Item -Path '.\AllowAllPolicy.xml' -Force

            $policyID = Set-CiPolicyIdInfo -FilePath "DenyPolicy $PolicyName.xml" -ResetPolicyID -PolicyName "$PolicyName"
            $policyID = $policyID.Substring(11)
            Set-CIPolicyVersion -FilePath "DenyPolicy $PolicyName.xml" -Version "1.0.0.0"
            
            @(0, 2, 5, 6, 11, 12, 16, 17, 19, 20) | ForEach-Object {
                Set-RuleOption -FilePath "DenyPolicy $PolicyName.xml" -Option $_ }
                
            @(3, 4, 9, 10, 13, 18) | ForEach-Object {
                Set-RuleOption -FilePath "DenyPolicy $PolicyName.xml" -Option $_ -Delete }
           
            Set-HVCIOptions -Strict -FilePath "DenyPolicy $PolicyName.xml"        
            ConvertFrom-CIPolicy "DenyPolicy $PolicyName.xml" "$policyID.cip" | Out-Null
            
            [PSCustomObject]@{
                DenyPolicyFile = "DenyPolicy $PolicyName.xml"
                DenyPolicyGUID = $PolicyID
            } 
            if ($Deployit) {                
                CiTool --update-policy "$policyID.cip" -json
                Remove-Item -Path "$policyID.cip" -Force
                Write-host -NoNewline "`n$policyID.cip for " -ForegroundColor Green
                Write-host -NoNewline "$PolicyName" -ForegroundColor Magenta
                Write-host " has been deployed." -ForegroundColor Green
            }   
        }
    } 
   
    <#
.SYNOPSIS
Creates Deny base policies (Windows Defender Application Control)
 
.LINK
https://github.com/HotCakeX/Harden-Windows-Security/wiki/New-DenyWDACConfig
 
.DESCRIPTION
Using official Microsoft methods to create Deny base policies (Windows Defender Application Control)
 
.COMPONENT
Windows Defender Application Control, ConfigCI PowerShell module
 
.FUNCTIONALITY
Using official Microsoft methods, Removes Signed and unsigned deployed WDAC policies (Windows Defender Application Control)
 
.PARAMETER Normal
Creates a Deny standalone base policy by scanning a directory for files. The base policy created by this parameter can be deployed side by side any other base/supplemental policy.
 
.PARAMETER Drivers
Creates a Deny standalone base policy for drivers only by scanning a directory for driver files. The base policy created by this parameter can be deployed side by side any other base/supplemental policy.
 
.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 "New-DenyWDACConfig" -ParameterName "ScanLocations" -ScriptBlock $ArgumentCompleterFolderPathsPicker