AppLockerAdmin.psm1
Function Invoke-ParallellJob { [CmdletBinding()] Param ( [Parameter(Mandatory)] [System.Management.Automation.ScriptBlock]$CodeBlock, [Parameter(Mandatory)] [String[]]$ComputerName, [System.Collections.Hashtable]$Parameters, [ValidateRange(1, 30)] [Int]$JobLimit = 10 ) Begin { Function Get-JobState { Param ($Job) $Flag = 'static','nonpublic','instance' $_Worker = $Job.PowerShell.GetType().GetField('worker', $Flag) $Worker = $_Worker.GetValue($Job.PowerShell) $_CRP = $worker.GetType().GetProperty('CurrentlyRunningPipeline',$Flag) $CRP = $_CRP.GetValue($Worker) If ($Job.handle.IsCompleted -AND -NOT [bool]$CRP) { 'Completed' } ElseIf (-NOT $Job.handle.IsCompleted -AND [bool]$CRP) { 'Running' } ElseIf (-NOT $Job.handle.IsCompleted -AND -NOT [bool]$CRP) { 'NotStarted' } } $RunSpacePool = [RunSpaceFactory]::CreateRunspacePool(1, $JobLimit) $Posh = [PowerShell]::Create() $Posh.RunSpacePool = $RunSpacePool $RunSpacePool.Open() $Jobs = [System.Collections.ArrayList]::new() } Process { ## Kick off jobs async foreach ($Computer in $ComputerName) { $PoshInstance = [powershell]::Create() $PoshInstance.RunSpacePool = $RunSpacePool [Void]$PoshInstance.AddScript($CodeBlock) [Void]$PoshInstance.AddParameter('ComputerName', $Computer) if ($PSBoundParameters.ContainsKey('Parameters')) { $Parameters.GetEnumerator() | ForEach-Object ` { [Void]$PoshInstance.AddParameter("$($_.Name)", $_.Value) } } $Handle = $PoshInstance.BeginInvoke() $Temp = '' | Select-Object -Property PowerShell, Handle $Temp.PowerShell = $PoshInstance $Temp.Handle = $Handle [Void]$Jobs.Add($Temp) } ## Monitor jobs $TotalJobCount = ($Jobs | Measure-Object).Count $ProgressId = Get-Random Write-Verbose -Message "Starting monitoring of $TotalJobCount jobs.." Do { $JobStates = Foreach ($Job in $Jobs) { Get-JobState -Job $Job } $NotStarted = ($JobStates | Where-Object { $_ -eq 'NotStarted' } | Measure-Object).Count $Running = ($JobStates | Where-Object { $_ -eq 'Running' } | Measure-Object).Count $Completed = ($JobStates | Where-Object { $_ -eq 'Completed' } | Measure-Object).Count Write-Progress -Activity "Getting AppLocker information.." -Status "Total $TotalJobCount | Not Started $($NotStarted) | Running $($Running) | Completed $($Completed)" -Id $ProgressId -PercentComplete ([Math]::Floor(($Completed / $TotalJobCount) * 100)) Start-Sleep -Seconds 1 } Until ($Completed -eq $TotalJobCount) ## Return data Write-Verbose -Message "Fetching data from jobs.." $Jobs | ForEach-Object { $_.PowerShell.EndInvoke($_.Handle) } } End { ## Cleanup Write-Verbose -Message "Cleaning up jobs.." $Jobs | ForEach-Object { $_.PowerShell.Dispose() } $RunSpacePool.Close() $RunSpacePool.Dispose() } } Function Get-AppLockerEvent { <# .SYNOPSIS Fetch AppLocker events from multiple system in parallell .DESCRIPTION Fetch AppLocker events from multiple system in parallell. Uses PowerShell runspace to perform fast remote code execution in parallell against multiple systems. Shows a progressbar to display progress. .EXAMPLE $Ev = Get-AppLockerEvent -ComputerName 'Server1', 'Server2' -MaxAgeHours 2 -Type Block -SkipPSScriptPolicyTest -Verbose $Ev = Get-AppLockerEvent -OU "OU=Servers,DC=domain,DC=local" -MaxAgeHours 2 -Type Block -SkipPSScriptPolicyTest -Verbose .NOTES Author: Martin Norlunn Updated: 12.06.2019 #> [Cmdletbinding(DefaultParameterSetName="ComputerName")] Param ( [Parameter(Mandatory, ParameterSetName="ComputerName")] [String[]]$ComputerName, [Parameter(Mandatory, ParameterSetName="OU")] [String]$OU, [Parameter(Mandatory, ParameterSetName="ADGroup")] [String]$ADGroup, [ValidateSet('All', 'Block', 'Audit')] [string]$EventType = 'All', [ValidateSet('All', 'EXE and DLL', 'MSI and Script', 'Packaged App')] [string]$FileType = 'All', [ValidateRange(1, 730)] [Int]$MaxAgeDays, [ValidateRange(1, 17520)] [Int]$MaxAgeHours, [Switch]$SkipAppDataTemp, [Switch]$SkipNormalUsers, [Switch]$SkipPSScriptPolicyTest ) $Watch = [System.Diagnostics.Stopwatch]::StartNew() if ($PSBoundParameters.ContainsKey('OU')) { $ComputerName = Get-ADComputer -Filter * -SearchBase $OU | Select-Object -ExpandProperty Name } elseif ($PSBoundParameters.ContainsKey('ADGroup')) { $ComputerName = Get-ADGroupMember -Identity "$ADGroup" | Where-Object objectClass -eq 'computer' | Select-Object -ExpandProperty Name } if ($PSBoundParameters.ContainsKey('MaxAgeDays')) { $Date = (Get-Date).AddDays(-1 * $MaxAgeDays) } elseif ($PSBoundParameters.ContainsKey('MaxAgeHours')) { $Date = (Get-Date).AddHours(-1 * $MaxAgeHours) } else { $Date = (Get-Date).AddYears(-2) } $EventId = switch ($EventType) { 'All' { 8003, 8004, 8006, 8007, 8024, 8025 } 'Block' { 8004, 8007, 8025 } 'Audit' { 8003, 8006, 8024 } } Write-Verbose -Message "StartDate = $($Date)" Write-Verbose -Message "EventId = $($EventId -join ', ')" Write-Verbose -Message "Collecting events.." $Parameters = @{ StartDate = $Date Id = $EventId FileType = $FileType } $Events = Invoke-ParallellJob -ComputerName $ComputerName -Parameters $Parameters -JobLimit 30 -Verbose:$VerbosePreference -CodeBlock ({ Param ($ComputerName, $StartDate, $Id) $Options = New-PSSessionOption -OpenTimeout (New-TimeSpan -Seconds 20) Invoke-Command -ComputerName $ComputerName -SessionOption $Options -ScriptBlock { Param($StartDate, $Id, $FileType) $Logs = @( "Microsoft-Windows-AppLocker/EXE and DLL" "Microsoft-Windows-AppLocker/MSI and Script" "Microsoft-Windows-AppLocker/Packaged app-Deployment" "Microsoft-Windows-AppLocker/Packaged app-Execution" ) if ($FileType -ne 'All') { $Logs = $Logs | Where-Object { $_ -like "*$FileType*" } } $Events = @() $AppLockerEvents = @() foreach ($Log in $Logs) { $Events += Get-WinEvent -FilterHashtable @{ LogName = $Log StartTime = $StartDate ID = @($Id) } -ErrorAction SilentlyContinue $AppLockerEvents += Get-AppLockerFileInformation -EventLog -LogPath $Log -EventType Audited -Statistics } $SIDTable = [Ordered]@{} $Events.UserId | Select-Object -Unique | ForEach-Object ` { $SIDTable.Add("$_", (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList "$_").Translate([System.Security.Principal.NTAccount]).Value) } foreach ($Ev in $Events) { $AEv = $AppLockerEvents | Where-Object { $Ev.Message -like "*$($_.FilePath.Path)*" } | Select-Object -First 1 New-Object -TypeName PSObject -Property @{ LogName = $Ev.LogName Id = $Ev.Id TimeCreated = $Ev.TimeCreated Message = $Ev.Message FilePublisher = $Aev.FilePublisher FilePath = $Aev.FilePath.Path FileHash = $Aev.FileHash User = $SIDTable[$Ev.UserId.Value] ComputerName = $Ev.MachineName } } } -ArgumentList $StartDate, $Id }) if ($SkipPSScriptPolicyTest.IsPresent) { Write-Verbose -Message "Skipping events of type __PSScriptPolicyTest_" $Events = $Events | Where-Object Message -notlike "*\APPDATA\LOCAL\TEMP\*__PSSCRIPTPOLICYTEST_*" } if ($SkipAppDataTemp.IsPresent) { Write-Verbose -Message "Skipping events with path matching C:\users\*\APPDATA\LOCAL\TEMP*" $Events = $Events | Where-Object Message -notlike "*C:\users\*\APPDATA\LOCAL\TEMP*" } if ($SkipNormalUsers.IsPresent) { Write-Verbose -Message "Including only service account and system events" $Events = $Events | Where-Object { ($_.User -split '\\' | Select-Object -Last 1) -like "svc_*" } } Write-Verbose -Message "Found $(($Events | Measure-Object).Count) events on $ComputerName" $Events | Select-Object -Property ComputerName, User, TimeCreated, LogName, Id, Message, FilePath, FilePublisher, FileHash $Watch.Stop() Write-Verbose -Message "Finished collecting events from $(($ComputerName | Measure-Object).Count) computer(s)" Write-Verbose -Message "Total script execution time: $($Watch.Elapsed.ToString())" } Function Get-AppLockerConfiguration { <# .SYNOPSIS Fetch AppLocker configuration from multiple system in parallell .DESCRIPTION Fetch AppLocker configuration from multiple system in parallell. Uses PowerShell runspace to perform fast remote code execution in parallell against multiple systems. Shows a progressbar to display progress. .EXAMPLE $Config = Get-AppLockerConfiguration -ComputerName 'Server1', 'Server2' -Verbose .EXAMPLE $Config = Get-AppLockerConfiguration -OU "OU=Servers,DC=domain,DC=local" -Verbose .EXAMPLE $Config = Get-AppLockerConfiguration -ADGroup MyComputerGroup -Verbose .NOTES Author: Martin Norlunn Updated: 12.06.2019 #> [Cmdletbinding(DefaultParameterSetName="ComputerName")] Param ( [Parameter(Mandatory, ParameterSetName="ComputerName")] [String[]]$ComputerName, [Parameter(Mandatory, ParameterSetName="OU")] [String]$OU, [Parameter(Mandatory, ParameterSetName="ADGroup")] [String]$ADGroup ) $Watch = [System.Diagnostics.Stopwatch]::StartNew() if ($PSBoundParameters.ContainsKey('OU')) { $ComputerName = Get-ADComputer -Filter * -SearchBase $OU | Select-Object -ExpandProperty Name } elseif ($PSBoundParameters.ContainsKey('ADGroup')) { $ComputerName = Get-ADGroupMember -Identity "$ADGroup" | Where-Object objectClass -eq 'computer' | Select-Object -ExpandProperty Name } Invoke-ParallellJob -ComputerName $ComputerName -JobLimit 30 -Verbose:$VerbosePreference -CodeBlock ({ Param ($ComputerName) $Options = New-PSSessionOption -OpenTimeout (New-TimeSpan -Seconds 20) Invoke-Command -ComputerName $ComputerName -SessionOption $Options -ScriptBlock { $Rules = ForEach ($Rule in ([XML](Get-AppLockerPolicy -Effective -Xml)).AppLockerPolicy.RuleCollection) { $Rule.FilePublisherRule | ForEach-Object { $_ | Select-Object -Property @{ Name = 'Type' Expression = { $Rule.Type } }, @{ Name = 'Enforced' Expression = { $Rule.EnforcementMode } }, Action, @{ Name = 'Principal' Expression = { (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $_.UserOrGroupSid).Translate([System.Security.Principal.NTAccount]).Value } }, Name, Description, @{ Name = 'Publisher' Expression = { $_.Conditions.FilePublisherCondition | Select-Object -Property PublisherName, ProductName, BinaryName, @{ Name = 'BinaryVersion' Expression = { if ($_.BinaryVersionRange.LowSection -eq '*' -and $_.BinaryVersionRange.HighSection -eq '*') { '*' } elseif ($_.BinaryVersionRange.LowSection -eq '*' -and $_.BinaryVersionRange.HighSection -ne '*') { "<$($_.BinaryVersionRange.HighSection)" } elseif ($_.BinaryVersionRange.LowSection -ne '*' -and $_.BinaryVersionRange.HighSection -eq '*') { ">$($_.BinaryVersionRange.HighSection)" } else { "$($_.BinaryVersionRange.LowSection) to $($_.BinaryVersionRange.HighSection)" } } } } } } $Rule.FilePathRule | ForEach-Object { $_ | Select-Object -Property @{ Name = 'Type' Expression = { $Rule.Type } }, @{ Name = 'Enforced' Expression = { $Rule.EnforcementMode } }, Action, @{ Name = 'Principal' Expression = { (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $_.UserOrGroupSid).Translate([System.Security.Principal.NTAccount]).Value } }, Name, Description, @{ Name = 'FilePath' Expression = { $_.Conditions.FilePathCondition.Path } } } } New-Object -TypeName PSObject -Property @{ ComputerName = $env:COMPUTERNAME Service = Get-Service -Name AppIdSvc | Select-Object -ExpandProperty Status RuleCollections = (Get-AppLockerPolicy -Effective).RuleCollections | Select-Object -Property RuleCollectionType, @{ Name = 'Enforced' Expression = { if ($_.EnforcementMode -eq 'Enabled'){ 'Yes' } else { 'No' } } }, Count Rules = $Rules Logs = @{ 'EXE and DLL' = @{ MaxSizeBytes = Get-WinEvent -ListLog 'Microsoft-Windows-AppLocker/EXE and DLL' | Select-Object -ExpandProperty MaximumSizeInBytes EventCount = Get-WinEvent -ListLog 'Microsoft-Windows-AppLocker/EXE and DLL' | Select-Object -ExpandProperty RecordCount } 'MSI and Script' = @{ MaxSizeBytes = Get-WinEvent -ListLog 'Microsoft-Windows-AppLocker/MSI and Script' | Select-Object -ExpandProperty MaximumSizeInBytes EventCount = Get-WinEvent -ListLog 'Microsoft-Windows-AppLocker/MSI and Script' | Select-Object -ExpandProperty RecordCount } 'PackagedAppDeployment' = @{ MaxSizeBytes = Get-WinEvent -ListLog 'Microsoft-Windows-AppLocker/Packaged app-Deployment' | Select-Object -ExpandProperty MaximumSizeInBytes EventCount = Get-WinEvent -ListLog 'Microsoft-Windows-AppLocker/Packaged app-Deployment' | Select-Object -ExpandProperty RecordCount } 'PackagedAppExecution' = @{ MaxSizeBytes = Get-WinEvent -ListLog 'Microsoft-Windows-AppLocker/Packaged app-Execution' | Select-Object -ExpandProperty MaximumSizeInBytes EventCount = Get-WinEvent -ListLog 'Microsoft-Windows-AppLocker/Packaged app-Execution' | Select-Object -ExpandProperty RecordCount } } } } | Select-Object -Property ComputerName, Service, RuleCollections, Rules, Logs }) $Watch.Stop() Write-Verbose -Message "Total script execution time: $($Watch.Elapsed.ToString())" } |