Public/Get-ApplicationSignatureAudit.ps1
|
#!/usr/bin/env pwsh Set-StrictMode -Version Latest if ($IsWindows) { function Get-ApplicationSignatureAudit { <# .SYNOPSIS Audits Authenticode signatures of discoverable applications. .DESCRIPTION Finds every application on the PATH with Get-Command, then checks each signature with Get-AuthenticodeSignature on parallel thread jobs, using Invoke-ParallelJob. Applications in WindowsApps are excluded. Each application produces a signature record, or an Error record when the check throws. Returns one summary with counts by outcome, every record in Results, and the inconclusive checks in Errors. Result order is not guaranteed. Windows only. .PARAMETER ThrottleLimit The maximum number of applications checked at once, from 1 through 255. Defaults to 75% of the processors, rounded down, and at least 1. .INPUTS None. You can't pipe objects to this function. .OUTPUTS System.Management.Automation.PSCustomObject. One summary with these properties: - TotalApplications: applications found. - AuditedApplications: checks with a conclusive status. - ValidApplications: status Valid. - UnsignedApplications: status NotSigned. - InvalidApplications: status HashMismatch or NotTrusted. - FailedApplications: inconclusive statuses and checks that threw. - Results: one record per application. - Errors: Path and Message of each failed check. Unsigned and invalid applications are not errors. No applications returns zero counts and empty arrays. .EXAMPLE $result = Get-ApplicationSignatureAudit $result.Results | Where-Object -Property Status -NE 'Valid' | Format-Table Name, Status, Path Lists the applications without a valid signature. .EXAMPLE Get-ApplicationSignatureAudit -ThrottleLimit 8 Checks up to eight applications at once. .EXAMPLE (Get-ApplicationSignatureAudit).Errors Lists the applications whose signature could not be checked. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(HelpMessage = 'Enter the maximum number of applications checked at once (1-255).')] [ValidateRange(1, 255)] [int]$ThrottleLimit ) if (-not $PSBoundParameters.ContainsKey('ThrottleLimit')) { $ThrottleLimit = Get-DefaultThrottleLimit } $applications = @( Get-Command -CommandType Application -All | Where-Object -Property Source -NotLike '*\AppData\Local\Microsoft\WindowsApps\*' | Select-Object -Property Name, Source ) $results = @( $applications | Invoke-ParallelJob -ThrottleLimit $ThrottleLimit { process { # Inside catch, $_ is the error, so keep the application. $application = $_ try { $signature = Get-AuthenticodeSignature -LiteralPath $application.Source -ErrorAction Stop $signer = if ($signature.SignerCertificate) { $signature.SignerCertificate.Subject } else { 'Unsigned' } $timeStamper = if ($signature.TimeStamperCertificate) { $signature.TimeStamperCertificate.Subject } else { 'None' } [PSCustomObject]@{ Name = $application.Name Path = $application.Source Status = $signature.Status.ToString() StatusMessage = $signature.StatusMessage SignerCertificate = $signer TimeStamper = $timeStamper IsOSBinary = $signature.IsOSBinary SignatureType = $signature.SignatureType.ToString() } } catch { [PSCustomObject]@{ Name = $application.Name Path = $application.Source Status = 'Error' StatusMessage = $_.Exception.Message SignerCertificate = 'Error' TimeStamper = 'None' IsOSBinary = $false SignatureType = 'Unknown' } } } } ) $conclusiveStatus = 'Valid', 'NotSigned', 'HashMismatch', 'NotTrusted' $failures = @($results | Where-Object -Property Status -NotIn $conclusiveStatus) [PSCustomObject]@{ TotalApplications = $applications.Count AuditedApplications = $applications.Count - $failures.Count ValidApplications = @($results | Where-Object -Property Status -EQ 'Valid').Count UnsignedApplications = @($results | Where-Object -Property Status -EQ 'NotSigned').Count InvalidApplications = @($results | Where-Object -Property Status -In 'HashMismatch', 'NotTrusted').Count FailedApplications = $failures.Count Results = $results Errors = @( foreach ($failure in $failures) { [PSCustomObject]@{ Path = $failure.Path; Message = $failure.StatusMessage } } ) } } } |