Authenticode.psm1
#Requires -version 2.0 function ConvertTo-StringData { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] $InputObject ) switch($InputObject.GetType().FullName) { 'System.Collections.Hashtable' { ($InputObject.Keys | % { "$_=$($InputObject.$_)" }) -join "`n" } } } function Get-UserCertificate { <# .SYNOPSIS Gets the user's default signing certificate so we don't have to ask them over and over... .DESCRIPTION The Get-UserCertificate function retrieves and returns a certificate from the user. It also stores the certificate so it can be reused without re-querying for the location and/or password ... .RETURNVALUE An X509Certificate2 suitable for code-signing #> [CmdletBinding()] param ( $Name ) begin { if($Name) { $Script:UserCertificate = Get-AuthenticodeCertificate $Name } } end { $ModuleManifest = Join-Path $PSScriptRoot Authenticode.psd1 if(Test-Path $ModuleManifest) { try { $OldCertificateString = (((Get-Content $ModuleManifest) -join "`n") -replace '(?s).*Certificates\s+=\s+ConvertFrom-StringData\s+"(.*)".*','$1') $Certificates = ConvertFrom-StringData $OldCertificateString } catch { $OldCertificateString = $null $Certificates = @{} } } else { $OldCertificateString = "" $Certificates = @{} } ## If they don't have a cert, or they haven't stored it... if(!(Test-Path Variable:Script:UserCertificate) -or ($Script:UserCertificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) -or ($Script:UserCertificate.Thumbprint -ne $Certificates.${Env:ComputerName}) ) { ## Verbose output if($VerbosePreference -gt "SilentlyContinue") { if(!(Test-Path Variable:Script:UserCertificate)) { Write-Verbose "Loading User Certificate from Module Config: $($Certificates.${Env:ComputerName} )" } else { Write-Verbose "Saving User Certificate to Module Config: ($($Script:UserCertificate.Thumbprint) -ne $($Certificates.${Env:ComputerName}))" } } Write-Debug "PrivateData: $($ExecutionContext.SessionState.Module | fl * | Out-String)" ## If they don't have a cert if(!(Test-Path Variable:Script:UserCertificate) -or $Script:UserCertificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) { $Script:UserCertificate = Get-AuthenticodeCertificate $Certificates.${Env:ComputerName} } Write-Verbose "Confirming Certificate: $($Script:UserCertificate.Thumbprint)" ## If their cert isn't stored at least temporarily... if($Script:UserCertificate -and (!$Certificates.${Env:ComputerName} -or ($Certificates.${Env:ComputerName} -ne $Script:UserCertificate.Thumbprint))) { ## Store it temporarily ... $Certificates.${Env:ComputerName} = $Script:UserCertificate.Thumbprint ## And ask them if they want to store it permanently Write-Verbose "Updating Module Metadata" if($Host.UI -and $Host.UI.PromptForChoice -and (0 -eq $Host.UI.PromptForChoice("Keep this certificate for future sessions?", $Script:UserCertificate, [Management.Automation.Host.ChoiceDescription[]]@("&Yes","&No"), 0)) ) { Write-Warning $Certificates $NewCertificateString = ConvertTo-StringData $Certificates if(!(Test-Path $ModuleManifest) -or $OldCertificateString -eq $Null) { Set-Content $ModuleManifest (( '@{', ' ModuleToProcess = "Authenticode.psm1"', ' ModuleVersion = "2.7"', ' FileList = "Authenticode.psm1", "Authenticode.psd1"', ' Author = "Joel Bennett"', ' CompanyName = "HuddledMasses.org"', ' Copyright = "Copyright (c) 2008-2015, Joel Bennett"', ' PowerShellVersion = "2.0"', ' Description = "Function wrappers for Authenticode Signing cmdlets"', ' PrivateData = @{', ' Certificates = ConvertFrom-StringData "', $NewCertificateString, ' "', ' PSData = @{', ' # Tags applied to this module. These help with module discovery in online galleries.', ' Tags = @("Authenticode","CodeSigning","Certificates")', ' # A URL to the license for this module.', ' LicenseUri = "http://opensource.org/licenses/ms-pl"', ' # A URL to the main website for this project.', ' ProjectUri = "https://github.com/Jaykul/Authenticode"', ' }', ' }', ' GUID = "4a14168f-41b8-4bc4-9ebf-83d5e6b84476"', ' FunctionsToExport = @("Set-AuthenticodeSignature","Get-AuthenticodeSignature","Test-AuthenticodeSignature",', ' "Select-AuthenticodeSigned","Start-AutoSign",', ' "Get-AuthenticodeCertificate","Get-UserCertificate")', ' AliasesToExport = "gas","sas","slas","sign"', '}' ) -join "`n") } else { Set-Content $ModuleManifest (((Get-Content $ModuleManifest) -join "`n") -replace '(?s)Certificates\s+=\s+ConvertFrom-StringData\s+"[\s\r\n]*(.*)[\s\r\n]*"', "Certificates = ConvertFrom-StringData `"`n`t$(${NewCertificateString} -replace "`n", "`n`t")`n`t`"") } } } } return $Script:UserCertificate } } function Get-AuthenticodeCertificate { [CmdletBinding()] param ( $Name = $(Get-UserCertificate) ) end { $Certificate = $Name # Until they get a cert, or hit ENTER without any input while($Certificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) { trap { Write-Host "The authenticode module requires a code-signing certificate, and can't find yours!" Write-Host Write-Host "If this is the first time you've seen this error, please run Get-AuthenticodeCertificate by hand and specify the full path to your PFX file, or the Thumbprint of a cert in your OS Cert store -- and then answer YES to save that cert in the 'PrivateData' of the Authenticode Module metadata." Write-Host Write-Host "If you have seen this error multiple times, you may need to manually create a module manifest for this module with the path to your cert, and/or specify the certificate name each time you use it." Write-Error $_ continue } ## If they haven't specified the name, prompt them: if(!$Name) { push-Location Cert:\ $certs = @(Get-ChildItem -Recurse -CodeSigningCert | Sort NotAfter) pop-Location if($certs.Count) { Write-Host "You have $($certs.Count) code signing certificates in your local certificate storage which you can specify by partial Thumbprint, or you may specify the path to a .pfx file:" -fore cyan $certs | Out-Host } $Name = $(Read-Host "Please specify a user certificate (wildcards allowed)") if(!$Name) { return } } Write-Verbose "Certificate Path: $Name" ## Check "CurrentUsers\My" first, because it's MOST LIKELY there, and it will be MUCH faster in some cases. $ResolvedPath = Get-ChildItem Cert:\CurrentUser\My -Recurse -CodeSigningCert | Where {$_.ThumbPrint -like $Name } | Select -Expand PSPath if(!$ResolvedPath) { ## We have to at least check the other folders too, if we didn't find it. $ResolvedPath = Get-ChildItem Cert:\ -Recurse -CodeSigningCert | Where {$_.ThumbPrint -like $Name } | Select -Expand PSPath } if(!$ResolvedPath) { Write-Verbose "Not a Certificate path: $Path" $ResolvedPath = Resolve-Path $Name -ErrorAction "SilentlyContinue" | Where { Test-Path $_ -PathType Leaf -ErrorAction "SilentlyContinue" } } if(!$ResolvedPath) { Write-Verbose "Not a full or legit relative path Path: $ResolvedPath" $ResolvedPath = Resolve-Path (Join-Path $PsScriptRoot $Name -ErrorAction "SilentlyContinue") -ErrorAction "SilentlyContinue" | Where { Test-Path $_ -PathType Leaf -ErrorAction "SilentlyContinue" } Write-Verbose "Resolved File Path: $ResolvedPath" } if(@($ResolvedPath).Count -gt 1) { throw "You need to specify enough of the name to narrow it to a single certificate. '$Name' returned $(@($ResolvedPath).Count):`n$($ResolvedPath|Out-String)" } $Certificate = get-item $ResolvedPath -ErrorAction "SilentlyContinue" if($Certificate -is [System.IO.FileInfo]) { $Certificate = Get-PfxCertificate $Certificate -ErrorAction "SilentlyContinue" } $Name = $Null # Blank it out so we re-prompt them } Write-Verbose "Certificate: $($Certificate | Out-String)" return $Certificate } } function Test-AuthenticodeSignature { <# .SYNOPSIS Tests a script signature to see if it is valid, or at least unaltered. .DESCRIPTION The Test-AuthenticodeSignature function processes the output of Get-AuthenticodeSignature to determine if it is Valid, OR **unaltered** and signed by the user's certificate .EXAMPLE ls *.ps1 | Get-AuthenticodeSignature | Where {Test-AuthenticodeSignature $_} To get the signature reports for all the scripts that we consider safely signed. .EXAMPLE ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature | Where {!(Test-AuthenticodeSignature $_ -Valid)} | gci | Set-AuthenticodeSignature This command gets information about the Authenticode signature in all of the script and module files, and tests the signatures, then re-signs all of the files that are not valid. .EXAMPLE ls | ? { gas $_ | Test-AuthenticodeSignature } List all the valid signed scripts (or scripts signed by our cert) .NOTES Test-AuthenticodeSignature returns TRUE even if the root CA certificate can't be verified, as long as the signing certificate's thumbnail matches the one specified by Get-UserCertificate. .INPUTTYPE System.Management.Automation.Signature .RETURNVALUE Boolean value representing whether the script's signature is valid, or YOUR certificate #> [CmdletBinding()] param ( # Specifies the signature object to test. This should be the output of Get-AuthenticodeSignature. [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)] $Signature, # Switch parameter, forces the signature to be valid -- otherwise, even if the certificate chain can't be verified, we will accept the cert which matches the "user" certificate (see Get-UserCertificate). # Aliases Valid [Alias("Valid")] [Switch]$ForceValid ) return ( $Signature.Status -eq "Valid" -or ( !$ForceValid -and ($Signature.Status -eq "UnknownError") -and ($_.SignerCertificate.Thumbprint -eq $(Get-UserCertificate).Thumbprint) ) ) } function Set-AuthenticodeSignature { <# .SYNOPSIS Adds an Authenticode signature to a Windows PowerShell script or other file. .DESCRIPTION The Set-AuthenticodeSignature function adds an Authenticode signature to any file that supports Subject Interface Package (SIP). In a Windows PowerShell script file, the signature takes the form of a block of text that indicates the end of the instructions that are executed in the script. If there is a signature in the file when this cmdlet runs, that signature is removed. .NOTES After the certificate has been validated, but before a signature is added to the file, the function checks the value of the $SigningApproved preference variable. If this variable is not set, or has a value other than TRUE, you are prompted to confirm the signing of the script. When specifying multiple values for a parameter, use commas to separate the values. For example, "<parameter-name> <value1>, <value2>". .EXAMPLE ls *.ps1 | Set-AuthenticodeSignature -Certificate $Certificate To sign all of the files with the specified certificate .EXAMPLE ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature | Where {!(Test-AuthenticodeSignature $_ -Valid)} | gci | Set-AuthenticodeSignature List all the script files, and get and test their signatures, and then sign all of the ones that are not valid, using the user's default certificate. .EXAMPLE Set-AuthenticodeSignature -Module PSCX Signs the whole PSCX module at once (all the ps1, psm1, psd1, dll, exe, and ps1xml files, etc.). .INPUTTYPE String. You can pipe a file path to Set-AuthenticodeSignature. .RETURNVALUE System.Management.Automation.Signature #> [CmdletBinding(DefaultParameterSetName="File")] param ( # Specifies the path to a file that is being signed. # Aliases Path, FullName [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="File")] [Alias("FullName")] [ValidateScript({ if((resolve-path $_).Provider.Name -ne "FileSystem") { throw "Specified Path is not in the FileSystem: '$_'" } return $true })] [string[]]$FilePath, # Specifies a module name (or path) to sign. # When you specify a module name, all of the files in that folder and it's subfolders are signed (if they are signable). [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="Module")] [Alias("ModuleName")] [string[]]$ModuleBase, # Specifies the certificate that will be used to sign the script or file. # Enter a certificate thumbprint (supports wildcards), or variable that stores the certificate or an expression that gets the certificate. # If the certificate is not valid or does not have code-signing authority, the command fails. # # To find a certificate, use Get-AuthenticodeCertificate # To set (or see) the default certificate, use Get-UserCertificate. [Parameter(Position=2, Mandatory=$false)] $Certificate, # Allows the cmdlet to append a signature to a read-only file. Even using the Force parameter, the cmdlet cannot override security restrictions. [Switch]$Force, # Specifies the hashing algorithm that Windows uses to compute the digital signature for the file. The default is SHA1, which is the Windows default hashing algorithm. # Files that are signed with a different hashing algorithm might not be recognized on other systems. [ValidateSet("SHA","MD5","SHA1","SHA256","SHA384","SHA512")] [String]$HashAlgorithm, #="SHA1" # Determines which certificates in the certificate trust chain are included in the digital signature. "NotRoot" is the default. # Valid values are: # -- Signer: Includes only the signer's certificate. # -- NotRoot: Includes all of the certificates in the certificate chain, except for the root authority. # -- All: Includes all the certificates in the certificate chain. [ValidateSet("Signer","NotRoot","All")] [String]$IncludeChain, #="NotRoot" # Uses the specified time stamp server to add a time stamp to the signature. Type the URL of the time stamp server as a string. # Defaults to Verisign's server: http://timestamp.verisign.com/scripts/timstamp.dll # The time stamp represents the exact time that the certificate was added to the file. A time stamp prevents the script from failing if the certificate expires because users and programs can verify that the certificate was valid atthe time of signing. [String]$TimestampServer = "http://timestamp.verisign.com/scripts/timstamp.dll" ) begin { Write-Verbose $("ParameterSetName Begin: " + $PSCmdlet.ParameterSetName) ## Can't specify this as a default value if we're in the pipeline, it doesn't get bound in time? if(!$Certificate) { $Certificate = Get-UserCertificate } if($Certificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) { $Certificate = Get-AuthenticodeCertificate $Certificate } $PSBoundParameters["Certificate"] = $Certificate $PSBoundParameters["TimestampServer"] = $TimestampServer } process { Write-Verbose $("ParameterSetName Process: " + $PSCmdlet.ParameterSetName) if($PSCmdlet.ParameterSetName -eq "Module"){ $ModuleBase = $ModuleBase | Split-Path -Leaf Write-Verbose "Signing Modules: $($ModuleBase -join ', ')" $FilePath = Get-Module -List $ModuleBase | Split-Path | Get-ChildItem -Recurse | Where-Object { !$_.PsIsContainer -and (".ps1",".psm1",".psd1",".ps1xml",".dll",".exe" -contains $_.Extension) } | Select-Object -Expand FullName $null = $PSBoundParameters.Remove("ModuleBase") Write-Verbose "Signing Files: $($FilePath | Out-String)" } Write-Verbose $("ParameterSetName 4: " + $PSCmdlet.ParameterSetName) foreach($file in @($FilePath)) { trap { Write-Warning "Could not sign file '$File' `n`n because $_"; continue } $PSBoundParameters.FilePath = $file if(Test-Path $file -Type Leaf) { Write-Verbose "Set Authenticode Signature on $FilePath with $($Certificate | Out-String)" $null = $PSBoundParameters.Remove("ModuleBase") Microsoft.PowerShell.Security\Set-AuthenticodeSignature @PSBoundParameters } else { $PSBoundParameters.FilePath = Get-ChildItem $file -Recurse | Where-Object { !$_.PsIsContainer -and (".ps1",".psm1",".psd1",".ps1xml",".dll",".exe" -contains $_.Extension) } | Select-Object -Expand FullName Microsoft.PowerShell.Security\Set-AuthenticodeSignature @PSBoundParameters } } return } } function Get-AuthenticodeSignature { <# .SYNOPSIS Gets information about the Authenticode signature in a file. .DESCRIPTION The Get-AuthenticodeSignature function gets information about the Authenticode signature in a file. If the file is not signed, the information is retrieved, but the fields are blank. .NOTES For information about Authenticode signatures in Windows PowerShell, type "get-help About_Signing". When specifying multiple values for a parameter, use commas to separate the values. For example, "-<parameter-name> <value1>, <value2>". .EXAMPLE Get-AuthenticodeSignature script.ps1 To get the signature information about the script.ps1 script file. .EXAMPLE ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature Get the signature information for all the script and data files .EXAMPLE ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature | Where {!(Test-AuthenticodeSignature $_ -Valid)} | gci | Set-AuthenticodeSignature This command gets information about the Authenticode signature in all of the script and module files, and tests the signatures, then signs all of the ones that are not valid. .INPUTTYPE String. You can pipe the path to a file to Get-AuthenticodeSignature. .RETURNVALUE System.Management.Automation.Signature ###################################################################################################> [CmdletBinding()] param ( # The path to the file(s) being examined. Wildcards are permitted, but they must lead to file(s). # Aliases Path, FullName [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Alias("FullName","Path")] [ValidateScript({ if((resolve-path $_).Provider.Name -ne "FileSystem") { throw "Specified Path is not in the FileSystem: '$_'" } if(!(Test-Path -PathType Leaf $_)) { throw "Specified Path is not a File: '$_'" } return $true })] [string[]] $FilePath ) process { Microsoft.PowerShell.Security\Get-AuthenticodeSignature -FilePath $FilePath } } function Select-AuthenticodeSigned { <# .SYNOPSIS Select files based on the status of their Authenticode Signature. .DESCRIPTION The Select-AuthenticodeSigned function filters files on the pipeline based on the state of their authenticode signature. .EXAMPLE ls | Select-AuthenticodeSigned -Mine -Broken | Set-AuthenticodeSignature Re-sign anything you signed before that has changed .EXAMPLE ls *.ps1,*.ps[dm]1 | Select-AuthenticodeSigned To get the signature information about the script.ps1 script file. .EXAMPLE ls *.ps1,*.psm1,*.psd1 | Get-AuthenticodeSignature Get the signature information for all the script and data files .NOTES For information about Authenticode signatures in Windows PowerShell, type "get-help About_Signing". When specifying multiple values for a parameter, use commas to separate the values. For example, "-<parameter-name> <value1>, <value2>". .INPUTTYPE String. You can pipe the path to a file to Get-AuthenticodeSignature. .RETURNVALUE System.Management.Automation.Signature ###################################################################################################> [CmdletBinding()] [OutputType("System.Management.Automation.Signature")] param ( # The path to the file(s) being examined. Wildcards are permitted, but they must lead to a single file. # Aliases Path, FullName [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Alias("FullName","Path")] [ValidateScript({ if((resolve-path $_).Provider.Name -ne "FileSystem") { throw "Specified Path is not in the FileSystem: '$_'" } return $true })] [string[]] $FilePath, # Return only files that are signed with the users' certificate (as returned by Get-UserCertificate). [Parameter()] [switch]$MineOnly, # Return only files that are NOT signed with the users' certificate (as returned by Get-UserCertificate). [Parameter()] [switch]$NotMineOnly, # Return only files with signatures that are broken (where the file has been edited, and the hash doesn't match). [Parameter()] [Alias("HashMismatch")] [switch]$BrokenOnly, # Returns the files that are Valid OR signed with the users' certificate (as returned by Get-UserCertificate). # # That is, TrustedOnly returns files returned by -ValidOnly OR -MineOnly (if you specify both parameters, you get only files that are BOTH -ValidOnly AND -MineOnly) [Parameter()] [switch]$TrustedOnly, # Return only files that are "Valid": This means signed with any cert where the certificate chain is verifiable to a trusted root certificate. This may or may not include files signed with the user's certificate. [Parameter()] [switch]$ValidOnly, # Return only files that doesn't have a "Valid" signature, which includes files that aren't signed, or that have a hash mismatch, or are signed by untrusted certs (possibly including the user's certificate). [Parameter()] [switch]$InvalidOnly, # Return only signable files that aren't signed at all. That is, only files that support Subject Interface Package (SIP) but aren't signed. [Parameter()] [switch]$UnsignedOnly ) process { if(!(Test-Path -PathType Leaf $FilePath)) { # if($ErrorAction -ne "SilentlyContinue") { # Write-Error "Specified Path is not a File: '$FilePath'" # } } else { foreach($sig in Get-AuthenticodeSignature -FilePath $FilePath) { # Broken only returns ONLY things which are HashMismatch if($BrokenOnly -and $sig.Status -ne "HashMismatch") { Write-Debug "$($sig.Status) - Not Broken: $FilePath" return } # Trusted only returns ONLY things which are Valid if($ValidOnly -and $sig.Status -ne "Valid") { Write-Debug "$($sig.Status) - Not Trusted: $FilePath" return } # AllValid returns only things that are SIGNED and not HashMismatch if($TrustedOnly -and (($sig.Status -ne "HashMismatch") -or !$sig.SignerCertificate) ) { Write-Debug "$($sig.Status) - Not Valid: $FilePath" return } # InvalidOnly returns things that are Either NotSigned OR HashMismatch ... if($InvalidOnly -and ($sig.Status -eq "Valid")) { Write-Debug "$($sig.Status) - Valid: $FilePath" return } # Unsigned returns only things that aren't signed # NOTE: we don't test using NotSigned, because that's only set for .ps1 or .exe files?? if($UnsignedOnly -and $sig.SignerCertificate ) { Write-Debug "$($sig.Status) - Signed: $FilePath" return } # Mine returns only things that were signed by MY CertificateThumbprint if($MineOnly -and (!($sig.SignerCertificate) -or ($sig.SignerCertificate.Thumbprint -ne $((Get-UserCertificate).Thumbprint)))) { Write-Debug "Originally signed by someone else, thumbprint: $($sig.SignerCertificate.Thumbprint)" Write-Debug "Does not match your default certificate print: $((Get-UserCertificate).Thumbprint)" Write-Debug " $FilePath" return } # NotMine returns only things that were NOT signed by MY CertificateThumbprint if($NotMineOnly -and (!($sig.SignerCertificate) -or ($sig.SignerCertificate.Thumbprint -eq $((Get-UserCertificate).Thumbprint)))) { if($sig.SignerCertificate) { Write-Debug "Originally signed by you, thumbprint: $($sig.SignerCertificate.Thumbprint)" Write-Debug "Matches your default certificate print: $((Get-UserCertificate).Thumbprint)" Write-Debug " $FilePath" } return } if(!$BrokenOnly -and !$TrustedOnly -and !$ValidOnly -and !$InvalidOnly -and !$UnsignedOnly -and !($sig.SignerCertificate) ) { Write-Debug "$($sig.Status) - Not Signed: $FilePath" return } get-childItem $sig.Path }} } } function Start-AutoSign { # .Synopsis # Start a FileSystemWatcher to automatically sign scripts when you save them # .Description # Create a FileSystemWatcher with a scriptblock that uses the Authenticode script Module to sign anything that changes # .Parameter Path # The path to the folder you want to monitor # .Parameter Filter # A filter to select only certain files: by default, *.ps* (because we can only sign .ps1, .psm1, .psd1, and .ps1xml # .Parameter Recurse # Whether we should also watch autosign files in subdirectories # .Parameter CertPath # The path or name of a certain certificate, to override the defaults from the Authenticode Module # .Parameter NoNotify # Whether wo should avoid using Growl to notify the user each time we sign something. # .Notes # Don't run this on a location where you're going to be generating hundreds of files ;) param($Path=".", $Filter= "*.ps*", [Switch]$Recurse, $CertPath, [Switch]$NoNotify) if(!$NoNotify -and (Get-Module Growl -ListAvailable -ErrorAction 0)) { Import-Module Growl Register-GrowlType AutoSign "Signing File" -ErrorAction 0 } else { $NoNotify = $false } $realItem = Get-Item $Path -ErrorAction Stop if (-not $realItem) { return } $Action = { ## Files that can't be signed show up as "UnknownError" with this message: $InvalidForm = "The form specified for the subject is not one supported or known by the specified trust provider" ## Files that are signed with a cert we don't trust also show up as UnknownError, but with different messages: # $UntrustedCert = "A certificate chain could not be built to a trusted root authority" # $InvalidCert = "A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider" # $ExpiredCert = "A required certificate is not within its validity period when verifying against the current system clock or the timestamp in the signed file" ForEach($file in Get-ChildItem $eventArgs.FullPath | Get-AuthenticodeSignature | Where-Object { $_.Status -ne "Valid" -and $_.StatusMessage -ne $invalidForm } | Select-Object -ExpandProperty Path ) { if(!$NoNotify) { Send-Growl AutoSign "Signing File" "File $($eventArgs.ChangeType), signing:" "$file" } if($CertPath) { Set-AuthenticodeSignature -FilePath $file -Certificate $CertPath } else { Set-AuthenticodeSignature -FilePath $file } } } $watcher = New-Object IO.FileSystemWatcher $realItem.Fullname, $filter -Property @{ IncludeSubdirectories = $Recurse } Register-ObjectEvent $watcher "Created" "AutoSignCreated$($realItem.Fullname)" -Action $Action > $null Register-ObjectEvent $watcher "Changed" "AutoSignChanged$($realItem.Fullname)" -Action $Action > $null Register-ObjectEvent $watcher "Renamed" "AutoSignChanged$($realItem.Fullname)" -Action $Action > $null } Set-Alias gas Get-AuthenticodeSignature -Description "Authenticode Module Alias" Set-Alias sas Set-AuthenticodeSignature -Description "Authenticode Module Alias" Set-Alias slas Select-AuthenticodeSigned -Description "Authenticode Module Alias" Set-Alias sign Set-AuthenticodeSignature -Description "Authenticode Module Alias" Export-ModuleMember -Alias gas,sas,slas,sign -Function Set-AuthenticodeSignature, Get-AuthenticodeSignature, Test-AuthenticodeSignature, Select-AuthenticodeSigned, Get-UserCertificate, Get-AuthenticodeCertificate, Start-AutoSign # SIG # Begin signature block # MIIXxAYJKoZIhvcNAQcCoIIXtTCCF7ECAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUu+Va8Cy2oCdXSwpacscHgmoL # 6sygghL3MIID7jCCA1egAwIBAgIQfpPr+3zGTlnqS5p31Ab8OzANBgkqhkiG9w0B # AQUFADCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIG # A1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhh # d3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcg # Q0EwHhcNMTIxMjIxMDAwMDAwWhcNMjAxMjMwMjM1OTU5WjBeMQswCQYDVQQGEwJV # UzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xMDAuBgNVBAMTJ1N5bWFu # dGVjIFRpbWUgU3RhbXBpbmcgU2VydmljZXMgQ0EgLSBHMjCCASIwDQYJKoZIhvcN # AQEBBQADggEPADCCAQoCggEBALGss0lUS5ccEgrYJXmRIlcqb9y4JsRDc2vCvy5Q # WvsUwnaOQwElQ7Sh4kX06Ld7w3TMIte0lAAC903tv7S3RCRrzV9FO9FEzkMScxeC # i2m0K8uZHqxyGyZNcR+xMd37UWECU6aq9UksBXhFpS+JzueZ5/6M4lc/PcaS3Er4 # ezPkeQr78HWIQZz/xQNRmarXbJ+TaYdlKYOFwmAUxMjJOxTawIHwHw103pIiq8r3 # +3R8J+b3Sht/p8OeLa6K6qbmqicWfWH3mHERvOJQoUvlXfrlDqcsn6plINPYlujI # fKVOSET/GeJEB5IL12iEgF1qeGRFzWBGflTBE3zFefHJwXECAwEAAaOB+jCB9zAd # BgNVHQ4EFgQUX5r1blzMzHSa1N197z/b7EyALt0wMgYIKwYBBQUHAQEEJjAkMCIG # CCsGAQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMBIGA1UdEwEB/wQIMAYB # Af8CAQAwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC50aGF3dGUuY29tL1Ro # YXd0ZVRpbWVzdGFtcGluZ0NBLmNybDATBgNVHSUEDDAKBggrBgEFBQcDCDAOBgNV # HQ8BAf8EBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFtcC0y # MDQ4LTEwDQYJKoZIhvcNAQEFBQADgYEAAwmbj3nvf1kwqu9otfrjCR27T4IGXTdf # plKfFo3qHJIJRG71betYfDDo+WmNI3MLEm9Hqa45EfgqsZuwGsOO61mWAK3ODE2y # 0DGmCFwqevzieh1XTKhlGOl5QGIllm7HxzdqgyEIjkHq3dlXPx13SYcqFgZepjhq # IhKjURmDfrYwggSjMIIDi6ADAgECAhAOz/Q4yP6/NW4E2GqYGxpQMA0GCSqGSIb3 # DQEBBQUAMF4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3Jh # dGlvbjEwMC4GA1UEAxMnU3ltYW50ZWMgVGltZSBTdGFtcGluZyBTZXJ2aWNlcyBD # QSAtIEcyMB4XDTEyMTAxODAwMDAwMFoXDTIwMTIyOTIzNTk1OVowYjELMAkGA1UE # BhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMTQwMgYDVQQDEytT # eW1hbnRlYyBUaW1lIFN0YW1waW5nIFNlcnZpY2VzIFNpZ25lciAtIEc0MIIBIjAN # BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAomMLOUS4uyOnREm7Dv+h8GEKU5Ow # mNutLA9KxW7/hjxTVQ8VzgQ/K/2plpbZvmF5C1vJTIZ25eBDSyKV7sIrQ8Gf2Gi0 # jkBP7oU4uRHFI/JkWPAVMm9OV6GuiKQC1yoezUvh3WPVF4kyW7BemVqonShQDhfu # ltthO0VRHc8SVguSR/yrrvZmPUescHLnkudfzRC5xINklBm9JYDh6NIipdC6Anqh # d5NbZcPuF3S8QYYq3AhMjJKMkS2ed0QfaNaodHfbDlsyi1aLM73ZY8hJnTrFxeoz # C9Lxoxv0i77Zs1eLO94Ep3oisiSuLsdwxb5OgyYI+wu9qU+ZCOEQKHKqzQIDAQAB # o4IBVzCCAVMwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAO # BgNVHQ8BAf8EBAMCB4AwcwYIKwYBBQUHAQEEZzBlMCoGCCsGAQUFBzABhh5odHRw # Oi8vdHMtb2NzcC53cy5zeW1hbnRlYy5jb20wNwYIKwYBBQUHMAKGK2h0dHA6Ly90 # cy1haWEud3Muc3ltYW50ZWMuY29tL3Rzcy1jYS1nMi5jZXIwPAYDVR0fBDUwMzAx # oC+gLYYraHR0cDovL3RzLWNybC53cy5zeW1hbnRlYy5jb20vdHNzLWNhLWcyLmNy # bDAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVGltZVN0YW1wLTIwNDgtMjAdBgNV # HQ4EFgQURsZpow5KFB7VTNpSYxc/Xja8DeYwHwYDVR0jBBgwFoAUX5r1blzMzHSa # 1N197z/b7EyALt0wDQYJKoZIhvcNAQEFBQADggEBAHg7tJEqAEzwj2IwN3ijhCcH # bxiy3iXcoNSUA6qGTiWfmkADHN3O43nLIWgG2rYytG2/9CwmYzPkSWRtDebDZw73 # BaQ1bHyJFsbpst+y6d0gxnEPzZV03LZc3r03H0N45ni1zSgEIKOq8UvEiCmRDoDR # EfzdXHZuT14ORUZBbg2w6jiasTraCXEQ/Bx5tIB7rGn0/Zy2DBYr8X9bCT2bW+IW # yhOBbQAuOA2oKY8s4bL0WqkBrxWcLC9JG9siu8P+eJRRw4axgohd8D20UaF5Mysu # e7ncIAkTcetqGVvP6KUwVyyJST+5z3/Jvz4iaGNTmr1pdKzFHTx/kuDDvBzYBHUw # ggUmMIIEDqADAgECAhACXbrxBhFj1/jVxh2rtd9BMA0GCSqGSIb3DQEBCwUAMHIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJ # RCBDb2RlIFNpZ25pbmcgQ0EwHhcNMTUwNTA0MDAwMDAwWhcNMTYwNTExMTIwMDAw # WjBtMQswCQYDVQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxFzAVBgNVBAcTDldl # c3QgSGVucmlldHRhMRgwFgYDVQQKEw9Kb2VsIEguIEJlbm5ldHQxGDAWBgNVBAMT # D0pvZWwgSC4gQmVubmV0dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB # AJfRKhfiDjMovUELYgagznWf+HFcDENk118Y/K6UkQDwKmVyVOvDyaVefjSmZZcV # NZqqYpm9d/Iajf2dauyC3pg3oay8KfXAADLHgbmbvYDc5zGuUNsTzMUOKlp9h13c # qsg898JwpRpI659xCQgJjZ6V83QJh+wnHvjA9ojjA4xkbwhGp4Eit6B/uGthEA11 # IHcFcXeNI3fIkbwWiAw7ZoFtSLm688NFhxwm+JH3Xwj0HxuezsmU0Yc/po31CoST # nGPVN8wppHYZ0GfPwuNK4TwaI0FEXxwdwB+mEduxa5e4zB8DyUZByFW338XkGfc1 # qcJJ+WTyNKFN7saevhwp02cCAwEAAaOCAbswggG3MB8GA1UdIwQYMBaAFFrEuXsq # CqOl6nEDwGD5LfZldQ5YMB0GA1UdDgQWBBQV0aryV1RTeVOG+wlr2Z2bOVFAbTAO # BgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAwbjA1 # oDOgMYYvaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1n # MS5jcmwwNaAzoDGGL2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3Vy # ZWQtY3MtZzEuY3JsMEIGA1UdIAQ7MDkwNwYJYIZIAYb9bAMBMCowKAYIKwYBBQUH # AgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgYQGCCsGAQUFBwEBBHgw # djAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUF # BzAChkJodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNz # dXJlZElEQ29kZVNpZ25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B # AQsFAAOCAQEAIi5p+6eRu6bMOSwJt9HSBkGbaPZlqKkMd4e6AyKIqCRabyjLISwd # i32p8AT7r2oOubFy+R1LmbBMaPXORLLO9N88qxmJfwFSd+ZzfALevANdbGNp9+6A # khe3PiR0+eL8ZM5gPJv26OvpYaRebJTfU++T1sS5dYaPAztMNsDzY3krc92O27AS # WjTjWeILSryqRHXyj8KQbYyWpnG2gWRibjXi5ofL+BHyJQRET5pZbERvl2l9Bo4Z # st8CM9EQDrdG2vhELNiA6jwenxNPOa6tPkgf8cH8qpGRBVr9yuTMSHS1p9Rc+ybx # FSKiZkOw8iCR6ZQIeKkSVdwFf8V+HHPrETCCBTAwggQYoAMCAQICEAQJGBtf1btm # dVNDtW+VUAgwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoT # DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UE # AxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoX # DTI4MTAyMjEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0 # IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNl # cnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBDQTCCASIwDQYJKoZIhvcN # AQEBBQADggEPADCCAQoCggEBAPjTsxx/DhGvZ3cH0wsxSRnP0PtFmbE620T1f+Wo # ndsy13Hqdp0FLreP+pJDwKX5idQ3Gde2qvCchqXYJawOeSg6funRZ9PG+yknx9N7 # I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrskacLCUvIUZ4qJRdQtoaPpiCwgla4cSocI # 3wz14k1gGL6qxLKucDFmM3E+rHCiq85/6XzLkqHlOzEcz+ryCuRXu0q16XTmK/5s # y350OTYNkO/ktU6kqepqCquE86xnTrXE94zRICUj6whkPlKWwfIPEvTFjg/Bougs # UfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8np+mM6n9Gd8lk9ECAwEAAaOCAc0wggHJ # MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoG # CCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29j # c3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MIGBBgNVHR8EejB4 # MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk # SURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln # aUNlcnRBc3N1cmVkSURSb290Q0EuY3JsME8GA1UdIARIMEYwOAYKYIZIAYb9bAAC # BDAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAoG # CGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7KgqjpepxA8Bg+S32ZXUOWDAfBgNVHSME # GDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkqhkiG9w0BAQsFAAOCAQEAPuwN # WiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh134LYP3DPQ/Er4v97yrfIFU3sOH20ZJ1 # D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63XX0R58zYUBor3nEZOXP+QsRsHDpEV+7qv # tVHCjSSuJMbHJyqhKSgaOnEoAjwukaPAJRHinBRHoXpoaK+bp1wgXNlxsQyPu6j4 # xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC/i9yfhzXSUWW6Fkd6fp0ZGuy62ZD2rOw # jNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG/AeB+ova+YJJ92JuoVP6EpQYhS6Skepo # bEQysmah5xikmmRR7zGCBDcwggQzAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYD # VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAv # BgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EC # EAJduvEGEWPX+NXGHau130EwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAI # oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB # CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFKoflTJ0GlyOX2eKxcHX # 8LYk4p+pMA0GCSqGSIb3DQEBAQUABIIBADnDmr5+cRNaH0B+OW8IDqZptMjoX48s # uyfG6uH1PtpU12uSOhEsh7WyCrHL83LlXqCt1cSFqMH1etQ+QH+aZkDIFb5RcY4D # 9WXQRSoliH7lsp3oDkPCKnJYZh/jpCkbHBHzMBnsteOBKlvUw1qqQ0FIkwCd2uz3 # qnZN0hGuLGwahVQ2DCmXWfbL5f14Cu4PgJIvtI8fK0FOaw2VAc8eAbxW5OlE4yLR # 5eZyZyU6x7rTep5f0Hbg6YOmxBLIylBWPBPK/1Fc8WrWdLSc8bbAAxORmfNfC5Ep # GlApcSbT/pGYvOS94HlkB5215e8mBNoJmzIGj0YdyKslmAjorQTpyNWhggILMIIC # BwYJKoZIhvcNAQkGMYIB+DCCAfQCAQEwcjBeMQswCQYDVQQGEwJVUzEdMBsGA1UE # ChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xMDAuBgNVBAMTJ1N5bWFudGVjIFRpbWUg # U3RhbXBpbmcgU2VydmljZXMgQ0EgLSBHMgIQDs/0OMj+vzVuBNhqmBsaUDAJBgUr # DgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx # DxcNMTUwNTA1MDUyOTU1WjAjBgkqhkiG9w0BCQQxFgQUKS7O0z1LgYirWVfxnRH6 # CT/6OIUwDQYJKoZIhvcNAQEBBQAEggEACURNO3bv5G+Uz/5liNvkOgPlwM/2CdEb # OqlMMVNbGNkG5X/tD+w7iJD44msdsX7AGKUhhy8Nyw/CFVrV1eH7y06d546zB1E9 # VqtqcD6HpCsxDQRdgTLqf/kzEP6F/LSC2k0EWfzfQjcg+7M/2suo1BlxOqWTQ1ej # 9QG0Qg/5UTKAzeU50rxAlwpWTnIb3UkYmEWu5G4sBCvij/6EPzH9/J0MgnZglNPO # vgDTy01fYyIPdr1IGqnIbOg00BfUbetVM0GXR5m7gHwYxSztMkGtAykdXmI22KD3 # 6Q/jrbqbdt/2S7eR6o3uLByK4iZM1IHXNp6jayKhJHms2T5XdzNLvg== # SIG # End signature block |