Version 14 of Reading version information from Win32 executables

Updated 2003-08-11 20:03:35

AF 24-07-03

 proc getdword {fh} {
    binary scan [read $fh 4] i* tmp
    return $tmp
 }

 proc getword {fh} {
    binary scan [read $fh 2] s* tmp
    return $tmp
 }

 proc getFixedInfo {file array} {
    set fh [open $file r]
    fconfigure $fh -encoding unicode -eofchar {}
    set data [read $fh]
    set s [string first "VS_VERSION_INFO" $data]
    if {$s < 0} {close $fh; error "no version information found"}
    unset data
    fconfigure $fh -encoding binary
    seek $fh [expr {($s * 2) - 6}] start
    seek $fh [expr {[tell $fh] % 4}] current
    binary scan [read $fh 6] sss len vlen type
    seek $fh 34 current
    if {[getdword $fh] != 4277077181} {close $fh; error "version information corrupt"}
    upvar $array ret
    array set ret {}

    seek $fh 4 current
    binary scan [read $fh 8] ssss b a d c
    set ret(FileVer) $a.$b.$c.$d
    binary scan [read $fh 8] ssss b a d c
    set ret(ProductVer) $a.$b.$c.$d
    seek $fh 4 current
    #binary scan [read $fh 4] B32 flagmask
    set ret(Flags) [getdword $fh]
    set ret(OS) [getdword $fh]
    set ret(FileType) [getdword $fh]
    set ret(FileSubType) [getdword $fh]
    binary scan [read $fh 8] w ret(Date)

    close $fh
 }

 proc getFixedInfo2 {file array} {
    set fh [open $file r]
    fconfigure $fh -encoding binary -eofchar {}
    set data [read $fh]
    set s [string first "VS_VERSION_INFO" $data]
    if {$s < 0} {close $fh; error "no version information found"}
    unset data
    seek $fh [expr {$s - 6}] start
    seek $fh [expr {[tell $fh] % 4}] current
    seek $fh 22 current
    if {[getdword $fh] != 4277077181} {close $fh; error "version information corrupt"}
    upvar $array ret
    array set ret {}

    seek $fh [expr {[tell $fh] % 4}] current
    binary scan [read $fh 8] ssss d c b a
    set ret(FileVer) $a.$b.$c.$d
    binary scan [read $fh 8] ssss d c b a
    set ret(ProductVer) $a.$b.$c.$d
    seek $fh 8 current
    set ret(Flags) [getdword $fh]
    set ret(OS) [getdword $fh]
    set ret(FileType) [getdword $fh]
    set ret(FileSubType) [getdword $fh]
    binary scan [read $fh 8] w ret(Date)
    close $fh
 }

 proc getStringInfo {file array} {
    upvar $array ret
    array set ret {}
    set fh [open $file r]
    fconfigure $fh -translation lf -encoding unicode -eofchar {}
    set data [read $fh]
    close $fh

    if {[set s [string first "StringFileInfo\000" $data]] < 0} {error "no string information found"}
    incr s -3

    if {![regexp {(.)\000(.)StringFileInfo\000(.)\000(.)(....)(....)\000} [string range $data $s end] --> len type len2 type2 lang code]} {
        error "string information corrupt"
    }
    array set ret [list Language $lang CodePage $code]
    set len [expr [scan $len %c] / 2]
    set len2 [expr [scan $len2 %c] / 2]
    set data [string range $data $s [expr {$s + $len}]]
    set s 30
    while {$s < $len2} {
        scan [string range $data $s end] %c%c%c slen vlen type
        if {$slen == 0} return
        set slen [expr {$slen / 2}]
        set name [string range $data [expr {$s + 3}] [expr {$s + $slen - $vlen - 1}]]
        set value [string range $data [expr {$s + $slen - $vlen}] [expr {$s + $slen - 2}]]
        set s [expr {$s + $slen + ($slen % 2)}]
        set ret([string trimright $name \000]) $value
    }
 }

 proc getStringInfo2 {file array} {
    upvar $array ret
    array set ret {}
    set fh [open $file r]
    fconfigure $fh -encoding binary -eofchar {}
    set data [read $fh]
    if {[set s [string first "StringFileInfo\000" $data]] < 0} {close $fh; error "no string information found"}

    seek $fh [expr {$s + 17}] start
    set len [getword $fh]
    seek $fh 2 current
    array set ret [list Lang [read $fh 4] CodePage [read $fh 4]]
    seek $fh 2 current
    seek $fh [expr {[tell $fh] % 4}] current
    set end [expr {$s + $len}]
    while {[tell $fh] < $end} {
        set slen [getword $fh]
        set vlen [getword $fh]
        set name [read $fh [expr {$slen - $vlen - 4}]]
        set value [read $fh [expr {$vlen + ($slen % 2)}]]
        seek $fh [expr {[tell $fh] % 4}] current
        set ret([string trimright $name \000]) [string trimright $value \000]
    }
 }

 proc writeStringInfo {file array} {
    upvar $array val
    set fh [open $file r+]
    fconfigure $fh -translation lf -encoding unicode -eofchar {}
    set data [read $fh]
    set s [string first "StringFileInfo\000" $data]
    if {$s < 0} { close $fh; error "no stringfileinfo found" }
    if {![info exists val(CodePage)]} { set val(CodePage) 04b0 }
    if {![info exists val(Language)]} { set val(Language) 0409 }
    incr s -3
    set len [scan [string index $data $s] %c]
    seek $fh [expr {$s * 2}] start

    puts -nonewline $fh [format "%c\000\001StringFileInfo\000%c\000\001%s%s\000" $len [expr {$len - 36}] $val(Language) $val(CodePage)]
    unset val(CodePage) val(Language)
    set olen $len
    set len [expr {($len / 2) - 30}]
    foreach x [array names val] {
        set vlen [expr {[string length $val($x)] + 1}]
        set nlen [string length $x]
        set npad [expr {$nlen % 2}]
        set tlen [expr {$vlen + $nlen + $npad + 4}]
        set tpad [expr {$tlen % 2}]

        if {($tlen + $tpad) > $len} { set error "too long" ; break }
        puts -nonewline $fh [format "%c%c\001%s\000%s%s\000%s" [expr {$tlen * 2}] $vlen $x [string repeat \000 $npad] $val($x) [string repeat \000 $tpad]]
        set len [expr {$len - $tlen - $tpad}]
    }
    puts -nonewline $fh [string repeat \000 $len]
    puts -nonewline $fh [string range $data [expr {$s + ($olen / 2)}] end]
    close $fh
    if {[info exists error]} { error $error }
 }

 proc readFixedInfo {file} {
    if {[catch {getFixedInfo $file results} err]} {
        puts "Error reading fixed file information: $err"
        return
    }

    puts "File version: $results(FileVer)"
    puts "Product version: $results(ProductVer)"

    set flags {}
    foreach x [lsort -integer -decreasing [array names ::ffi_flags]] {
        if {$results(Flags) > $x} {
            incr results(Flags) -$x
            lappend flags $::ffi_flags($x)
        }
    }
    if {$flags == ""} { set flags None }
    puts "Flags: [join $flags ", "]"

    set blah {}
    if {$results(OS) == 0} {
        puts "OS: Unknown"
    } else {
        foreach x [lsort -integer -decreasing [array names ::ffi_os]] {
            if {$results(OS) >= $x} {
                incr results(OS) -$x
                lappend blah $x
            }
        }
        switch -exact -- [llength $blah] {
            0       { puts "OS: Unidentified" }
            1       { puts "OS: $::ffi_os([lindex $blah 0])" }
            default { puts "OS: $::ffi_os([lindex $blah 1]) on $::ffi_os([lindex $blah 0])" }
        }
    }

    if {[info exists ::ffi_type($results(FileType))]} {
        puts "File Type: $::ffi_type($results(FileType))"
    } else {
        puts "File Type: Unidentified"
    }

    if {$results(FileType) != 3 && $results(FileType) != 4} {
    } elseif {[info exists ::ffi_${ft}_subtype($results(FileSubType))]} {
        puts "File SubType: [set ::ffi_${ft}_subtype($results(FileSubType))]"
    } else {
        puts "File SubType: Unidentified"
    }

    if {$results(Date) == 0} {
        puts "Date: None"
    } else {
        puts "Date: [clock format $results(Date) -gmt 1]"
    }
 }

 proc readStringInfo {file} {
    if {[catch {getStringInfo $file results} err]} {
        puts "Error reading string file information: $err"
        return
    }
    foreach x [array names results] {
        puts "$x: $results($x)"
    }
 }

 array set ffi_os {
    1      "Windows 16bit"
    2      "Presentation Manager 16bit"
    3      "Presentation Manager 32bit"
    4      "Windows 32bit"
    65536   DOS
    131072 "OS/2 16bit"
    196608 "OS/2 32bit"
    262144 "Windows NT"
 }

 array set ffi_flags {
    1 Debug
    2 Prerelease
    4 Patched
    8 "Private Build"
    16 "Info Inferred"
    32 "Special Build"
 }

 array set ffi_type {
    0 Unknown
    1 Application
    2 DLL
    3 Driver
    4 Font
    5 VXD
    7 "Static Library"
 }

 array set ffi_3_subtype {
    0 Unknown
    1 Printer
    2 Keyboard
    3 Language
    4 Display
    5 Mouse
    6 Network
    7 System
    8 Installable
    9 Sound
    10 Communications

}

 array set ffi_4_subtype {
    0 Unknown
    1 Raster
    2 Vector
    3 TrueType
 }

the get* procs represent a programatic interface to the information. the read* procs output this information in a nice human readable format. the *2 procs exist because there are 2 slightly different types of files, the newer ones are unicode.

output:

 getFixedInfo tclkit.exe test
 parray test

 test(Date)        = 0
 test(FileSubType) = 0
 test(FileType)    = 2
 test(FileVer)     = 8.4.2.2
 test(Flags)       = 0
 test(OS)          = 4
 test(ProductVer)  = 8.4.2.2


 getStringInfo tclkit.exe test
 parray test

 test(CodePage)         = 04b0
 test(CompanyName)      = Equi4 Software
 test(FileDescription)  = Tclkit, a standalone runtime for Tcl/Tk
 test(FileVersion)      = 8.4.2
 test(Language)         = 0409
 test(LegalCopyright)   = Copyright © 1989-2003 by J.Ousterhout et al.
 test(OriginalFilename) = tclkit.exe
 test(ProductName)      = Tclkit 8.4 for Windows
 test(ProductVersion)   = 8.4.2


 readFixedInfo tclkit.exe

 File version: 8.4.2.2
 Product version: 8.4.2.2
 Flags: None
 OS: Windows 32bit
 File Type: DLL
 Date: None

i support writing any values to the file but here is a list of the ones normally used.

Comments CompanyName FileDescription FileVersion InternalName LegalCopyright LegalTrademarks OriginalFilename PrivateBuild ProductName ProductVersion SpecialBuild

in addition i use CodePage and Language

you can only write to files that already have a stringfileinfo block and you are limited to the same or less total length so as not to corrupt the file.


some relevant links

http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/resources/versioninformation/versioninformationreference/versioninformationstructures/stringfileinfo.asp

http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/resources/versioninformation/versioninformationreference/versioninformationstructures/stringtable.asp

http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/resources/versioninformation/versioninformationreference/versioninformationstructures/string.asp