@@ -20,10 +20,14 @@ import (
2020var (
2121 modntdll = windows .NewLazyDLL ("ntdll.dll" )
2222 modkernel = windows .NewLazyDLL ("kernel32.dll" )
23+ modversion = windows .NewLazyDLL ("version.dll" )
2324 procNtQueryInformationProcess = modntdll .NewProc ("NtQueryInformationProcess" )
2425 procReadProcessMemory = modkernel .NewProc ("ReadProcessMemory" )
2526 procIsWow64Process = modkernel .NewProc ("IsWow64Process" )
2627 procQueryFullProcessImageNameW = modkernel .NewProc ("QueryFullProcessImageNameW" )
28+ procGetFileVersionInfoSizeW = modversion .NewProc ("GetFileVersionInfoSizeW" )
29+ procGetFileVersionInfoW = modversion .NewProc ("GetFileVersionInfoW" )
30+ procVerQueryValueW = modversion .NewProc ("VerQueryValueW" )
2731)
2832
2933// C definition from winternl.h
@@ -480,3 +484,92 @@ func IsCurrentProcessLocalSystem() (bool, error) {
480484
481485 return currentUser .Equals (localSystem ), nil
482486}
487+
488+ // GetFileDescription returns the file description for given executable path
489+ func GetFileDescription (executablePath string ) (string , error ) {
490+ // Convert path to UTF16
491+ pathPtr , err := syscall .UTF16PtrFromString (executablePath )
492+ if err != nil {
493+ return "" , fmt .Errorf ("failed to convert path to UTF16: %w" , err )
494+ }
495+
496+ // Get the size of the version information
497+ var handle uint32
498+ size , _ , err := procGetFileVersionInfoSizeW .Call (
499+ uintptr (unsafe .Pointer (pathPtr )),
500+ uintptr (unsafe .Pointer (& handle )),
501+ )
502+ if size == 0 {
503+ if err != nil && err != syscall .Errno (0 ) {
504+ return "" , fmt .Errorf ("GetFileVersionInfoSizeW failed: %w" , err )
505+ }
506+ return "" , fmt .Errorf ("no version information available for %s" , executablePath )
507+ }
508+
509+ // Allocate buffer for version info
510+ data := make ([]byte , size )
511+
512+ // Get the version information
513+ ret , _ , err := procGetFileVersionInfoW .Call (
514+ uintptr (unsafe .Pointer (pathPtr )),
515+ uintptr (handle ),
516+ uintptr (size ),
517+ uintptr (unsafe .Pointer (& data [0 ])),
518+ )
519+ // returns non-zero if successful, and zero if not
520+ if ret == 0 {
521+ return "" , fmt .Errorf ("GetFileVersionInfoW failed: %w" , err )
522+ }
523+
524+ // Query the language and code page
525+ // First get the translation table
526+ subBlockPtr , err := syscall .UTF16PtrFromString ("\\ VarFileInfo\\ Translation" )
527+ if err != nil {
528+ return "" , fmt .Errorf ("failed to create subblock string: %w" , err )
529+ }
530+
531+ var langCodePagePtr * uint16
532+ var langCodePageLen uint32
533+ ret , _ , err = procVerQueryValueW .Call (
534+ uintptr (unsafe .Pointer (& data [0 ])),
535+ uintptr (unsafe .Pointer (subBlockPtr )),
536+ uintptr (unsafe .Pointer (& langCodePagePtr )),
537+ uintptr (unsafe .Pointer (& langCodePageLen )),
538+ )
539+
540+ var langCodePage string
541+ if ret == 0 || langCodePageLen < 4 {
542+ return "" , fmt .Errorf ("no language code page found: %w" , err )
543+ }
544+
545+ pair := (* [2 ]uint16 )(unsafe .Pointer (langCodePagePtr ))
546+
547+ // Extract the first language/codepage pair
548+ langCode := pair [0 ]
549+ codePage := pair [1 ]
550+ langCodePage = fmt .Sprintf ("%04x%04x" , langCode , codePage )
551+
552+ // Query for FileDescription
553+ fileDescQuery := fmt .Sprintf ("\\ StringFileInfo\\ %s\\ FileDescription" , langCodePage )
554+ fileDescQueryPtr , err := syscall .UTF16PtrFromString (fileDescQuery )
555+ if err != nil {
556+ return "" , fmt .Errorf ("failed to create file description query: %w" , err )
557+ }
558+
559+ var fileDescPtr * uint16
560+ var fileDescLen uint32
561+ ret , _ , err = procVerQueryValueW .Call (
562+ uintptr (unsafe .Pointer (& data [0 ])),
563+ uintptr (unsafe .Pointer (fileDescQueryPtr )),
564+ uintptr (unsafe .Pointer (& fileDescPtr )),
565+ uintptr (unsafe .Pointer (& fileDescLen )),
566+ )
567+
568+ if ret == 0 || fileDescLen == 0 {
569+ return "" , fmt .Errorf ("FileDescription not found in version info: %w" , err )
570+ }
571+
572+ // Convert the UTF16 string to Go string
573+ fileDesc := windows .UTF16PtrToString ((* uint16 )(unsafe .Pointer (fileDescPtr )))
574+ return fileDesc , nil
575+ }
0 commit comments