Why do SignTool/Authenticode Message Digests not match the direct hash of the file? I'm attempting to validate an Authenticode signature (generated by signtool /fd SHA256 /f &lt;code signing PFX&gt; /p &lt;PFX password&gt; test.exe), and I've run into an issue that I've narrowed down to a single point of confusion.

For the command above, if I hash test.exe before running the signing operation, for example using Get-FileHash test.exe in Powershell, I get a SHA256 hash of the file. But after signing the file, inspecting the signature in windows (Properties -> Digital Signatures -> Details -> Advanced) shows a "Message Digest" value which is entirely different from the hash of test.exe as calculated before signing.

Windows reports this signature as valid, so there is clearly a consistent understanding of "Message Digest" that isn't the one I have in my head (hash of the raw file contents, excluding signature). Can somebody please explain to me what Message Digest means in this context, and how it differs from a regular hashing operation?
You are expecting that the hash that is produced is always over the complete file. This is not correct. Authenticode uses the Cryptographic Message Syntax - a container format - to embed the content and the signature. This ensures that the Authenticode signature is using the hash of the original data (+ other signed attributes). The Authenticode format is specified here.

To quote:


  A PKCS#7 SignedDatastructure contains the PE file's hash value, a signature created by the software publisher’s private key, and the X.509 v3 certificates that bind the software publisher’s signing key to a legal entity. A PKCS#7 SignedDatastructure can optionally contain:
  
  
  A description of the software publisher.
  The software publisher's URL.
  An Authenticode timestamp.
  


Note that Microsoft keeps interchanging the name of the standard with the name of the structure that it defines: PKCS#7 is the standard that defines the Cryptographic Message Syntax.

Unfortunately, going through all the steps of "Calculating the PE Image Hash" is a bit too much for this answer; if you plan to do this programmatically you could ask questions about it on StackOverflow.