zoukankan      html  css  js  c++  java
  • Wix 安装部署教程(十三) -- 多语言安装包

          这几天摸索WIX的多语言安装包(这里是Wix的setup 工程,不是Bundle),终于走通了,感谢网友uni的指点。WIX的多语言安装包能够根据系统环境自动切换界面语言,你也可以通过命令指定语言。下面我说一说步骤。共4步。

          1.设置WixLocalization文件。

          Wxl文件就相当于应用程序的资源文件。让我们根据不同的语言来编写不同的文本内容。

          

          例如我们新建一个WixUI_zh-cn.wxl ,来处理简体中文。

    <WixLocalization Culture="zh-cn" Codepage="936" xmlns="http://schemas.microsoft.com/wix/2006/localization">
      <String Id="WixUIBack" Overridable="yes">上一步(&amp;B)</String>
      <String Id="WixUINext" Overridable="yes">下一步(&amp;N)</String>
      <String Id="WixUICancel" Overridable="yes">取消</String>
      <String Id="WixUIFinish" Overridable="yes">完成(&amp;F)</String>
      <String Id="WixUIRetry" Overridable="yes">重试(&amp;R)</String>
      <String Id="WixUIIgnore" Overridable="yes">忽略(&amp;I)</String>
      <String Id="WixUIYes" Overridable="yes">是(&amp;Y)</String>
      <String Id="WixUINo" Overridable="yes">否(&amp;N)</String>
      <String Id="WixUIOK" Overridable="yes">确定</String>
      <String Id="WixUIPrint" Overridable="yes">打印(&amp;P)</String>
    ......

       这些主要是用于UI界面上的元素,我们看Wix的窗口源码:下面的!(loc.WixUINext)和!(loc.WixUICancel) 和上面的Id为WixUINext和Id为WixUICancel的String 一一 对应。而属性Overridable,表示可以重写。因为默认是英文,可以重写的意思就是最新的String会覆盖之前的同名Id。

    <Dialog Id="WelcomeDlg"  Width="370"  Title="!(loc.WelcomeDlg_Title)">
    <Control Id="Next"  Type="PushButton"  X="236"  Y="243"  Width="56"Height="17"  Default="yes"   Text="!(loc.WixUINext)" />
    <Control Id="Cancel" Type="PushButton" X="304" Y="243"   Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
    <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
    </Control>

    如果要添加自定义的内容,添加String就行,另外我们可以看见,这个wxl文件有Culture="zh-cn" Codepage="936"两个属性,这是和Product的Language属性和Codepage属性对应,而Product的Codepage属性不能使用!loc.Id 的方式,但Wix会根据wxl文件进行重载。Package的Languages属性和SummaryCodepage 也是一样对应。 

     

     <String Id="Lang">2052</String>
      <String Id="Code">936</String>

    下面是三种语言的Language和Codepage。

      语言                              语言-国家     Language      Codepage
    English                            en-us             1033               1252
    Simplified Chinese            zh-cn              2052               936
    Traditional Chinese           zh-tw              1028                950

     同理我们分别添加英文和繁体的wxl。更多的源文件在源码的下面的目录可以找到:

     WIXwix-3.8srcextUIExtensionwixlib

     最终我们得到:

    这个时候不同语言设置的工作我们已经完成。下面设置下属性--Build--Cultures to build,生成安装包。

     

     但是我们看到这三个安装文件,这不是我们想要的。毕竟三个文件都是一样的大。 我们继续处理。将多个msi文件融合成一个。

    2.生成mst文件

     这里要用到transform files(变形文件?恩,就这么翻译吧) 一个变形文件(.mst)包含了两个msi文件的比较内容,在做Path的时候,也会用到它。既然是比较内容,就需要有一个文件作为基础,这次我们选英文。另外我们会用到一个wix工具:Torch.exe,它在Wix的安装目录bin下面可以找到。它的命令语法如下:

    torch.exe [-?] [options] targetInput updatedInput -out outputFile  [@responseFile]

    示例:

    torch.exe -t language "en-usMyInstaller.msi""zh-cnMyInstaller.msi" -out "transformszn-cn.mst"

    别忘了用管理员身份执行,还要带上正确的路径。生成的mst文件再wix的bin目录,也就是和torch.exe的同级目录。如下图所示,操作正确会出现 Windows Installer XML Toolset。。。的字样。

     同理我们生成zh-tw.mst和zh-cn.mst,要生成en-us.mst,就把中文和英文的位置换一下。 生成文件如下。

    3.合并mst文件。

    步骤2相当于我们拿到三个界面的资源文件,现在就是需要把他们合并成一个文件,也就是把mst嵌入到msi文件中。WIX本身没有提供工具做这个事情,但Windows 的SDK 有几个VBScript文件能执行数据库(MSI本身就是一种数据库文件)相关的任务。你需要从MSDN下载安装SDK,这些脚本包含在Win32的示例中。你也可以从下面的链接了解更多内容。

    https://msdn.microsoft.com/en-us/library/aa372865(VS.85).aspx

    而我们感兴趣的脚本是WixSubStg.vbs. win7系统,它的目录应该是在C:Program FilesMicrosoft SDKsWindowsv7.0Samplessysmgmtmsiscripts. 中。找到之后我们复制到安装工程的Debug目录下。

    WixSubStg.vbs:

    ' Windows Installer utility to add a transform or nested database as a substorage
    ' For use with Windows Scripting Host, CScript.exe or WScript.exe
    ' Copyright (c) Microsoft Corporation. All rights reserved.
    ' Demonstrates the use of the database _Storages table
    '
    Option Explicit
    
    Const msiOpenDatabaseModeReadOnly     = 0
    Const msiOpenDatabaseModeTransact     = 1
    Const msiOpenDatabaseModeCreate       = 3
    
    Const msiViewModifyInsert         = 1
    Const msiViewModifyUpdate         = 2
    Const msiViewModifyAssign         = 3
    Const msiViewModifyReplace        = 4
    Const msiViewModifyDelete         = 6
    
    Const ForAppending = 8
    Const ForReading = 1
    Const ForWriting = 2
    Const TristateTrue = -1
    
    ' Check arg count, and display help if argument not present or contains ?
    Dim argCount:argCount = Wscript.Arguments.Count
    If argCount > 0 Then If InStr(1, Wscript.Arguments(0), "?", vbTextCompare) > 0 Then argCount = 0
    If (argCount = 0) Then
        Wscript.Echo "Windows Installer database substorage managment utility" &_
            vbNewLine & " 1st argument is the path to MSI database (installer package)" &_
            vbNewLine & " 2nd argument is the path to a transform or database to import" &_
            vbNewLine & " If the 2nd argument is missing, substorages will be listed" &_
            vbNewLine & " 3rd argument is optional, the name used for the substorage" &_
            vbNewLine & " If the 3rd arugment is missing, the file name is used" &_
            vbNewLine & " To remove a substorage, use /D or -D as the 2nd argument" &_
            vbNewLine & " followed by the name of the substorage to remove" &_
            vbNewLine &_
            vbNewLine & "Copyright (C) Microsoft Corporation.  All rights reserved."
        Wscript.Quit 1
    End If
    
    ' Connect to Windows Installer object
    On Error Resume Next
    Dim installer : Set installer = Nothing
    Set installer = Wscript.CreateObject("WindowsInstaller.Installer") : CheckError
    
    ' Evaluate command-line arguments and set open and update modes
    Dim databasePath:databasePath = Wscript.Arguments(0)
    Dim openMode    : If argCount = 1 Then openMode = msiOpenDatabaseModeReadOnly Else openMode = msiOpenDatabaseModeTransact
    Dim updateMode  : If argCount > 1 Then updateMode = msiViewModifyAssign  'Either insert or replace existing row
    Dim importPath  : If argCount > 1 Then importPath = Wscript.Arguments(1)
    Dim storageName : If argCount > 2 Then storageName = Wscript.Arguments(2)
    If storageName = Empty And importPath <> Empty Then storageName = Right(importPath, Len(importPath) - InStrRev(importPath, "",-1,vbTextCompare))
    If UCase(importPath) = "/D" Or UCase(importPath) = "-D" Then updateMode = msiViewModifyDelete : importPath = Empty 'substorage will be deleted if no input data
    
    ' Open database and create a view on the _Storages table
    Dim sqlQuery : Select Case updateMode
        Case msiOpenDatabaseModeReadOnly: sqlQuery = "SELECT `Name` FROM _Storages"
        Case msiViewModifyAssign:         sqlQuery = "SELECT `Name`,`Data` FROM _Storages"
        Case msiViewModifyDelete:         sqlQuery = "SELECT `Name` FROM _Storages WHERE `Name` = ?"
    End Select
    Dim database : Set database = installer.OpenDatabase(databasePath, openMode) : CheckError
    Dim view     : Set view = database.OpenView(sqlQuery)
    Dim record
    
    If openMode = msiOpenDatabaseModeReadOnly Then 'If listing storages, simply fetch all records
        Dim message, name
        view.Execute : CheckError
        Do
            Set record = view.Fetch
            If record Is Nothing Then Exit Do
            name = record.StringData(1)
            If message = Empty Then message = name Else message = message & vbNewLine & name
        Loop
        Wscript.Echo message
    Else 'If adding a storage, insert a row, else if removing a storage, delete the row
        Set record = installer.CreateRecord(2)
        record.StringData(1) = storageName
        view.Execute record : CheckError
        If importPath <> Empty Then  'Insert storage - copy data into stream
            record.SetStream 2, importPath : CheckError
        Else  'Delete storage, fetch first to provide better error message if missing
            Set record = view.Fetch
            If record Is Nothing Then Wscript.Echo "Storage not present:", storageName : Wscript.Quit 2
        End If
        view.Modify updateMode, record : CheckError
        database.Commit : CheckError
        Set view = Nothing
        Set database = Nothing
        CheckError
    End If
    
    Sub CheckError
        Dim message, errRec
        If Err = 0 Then Exit Sub
        message = Err.Source & " " & Hex(Err) & ": " & Err.Description
        If Not installer Is Nothing Then
            Set errRec = installer.LastErrorRecord
            If Not errRec Is Nothing Then message = message & vbNewLine & errRec.FormatText
        End If
        Wscript.Echo message
        Wscript.Quit 2
    End Sub
    View Code

    打开cmd 执行下面的命令:

     WiSubStg.vbs "en-usDIAViewSetup.msi" "transformszh-cn.mst" 2052
    WiSubStg.vbs "en-usDIAViewSetup.msi" "transformszh-tw.mst" 1028

     等于是在英文的安装包里面嵌入了简体和繁体的语言变形。 所以我们不必嵌入英文的变形。 完成了这个步骤还不够。我们还需要改变安装包Packages的Languages属性,告诉它支持的语言类型有哪些。而这需要用到另外一个脚本文件

    WiLangId.vbs:也需要放于Debug目录下面。

    ' Windows Installer utility to report the language and codepage for a package
    ' For use with Windows Scripting Host, CScript.exe or WScript.exe
    ' Copyright (c) Microsoft Corporation. All rights reserved.
    ' Demonstrates the access of language and codepage values                 
    '
    Option Explicit
    
    Const msiOpenDatabaseModeReadOnly     = 0
    Const msiOpenDatabaseModeTransact     = 1
    Const ForReading = 1
    Const ForWriting = 2
    Const TristateFalse = 0
    
    Const msiViewModifyInsert         = 1
    Const msiViewModifyUpdate         = 2
    Const msiViewModifyAssign         = 3
    Const msiViewModifyReplace        = 4
    Const msiViewModifyDelete         = 6
    
    Dim argCount:argCount = Wscript.Arguments.Count
    If argCount > 0 Then If InStr(1, Wscript.Arguments(0), "?", vbTextCompare) > 0 Then argCount = 0
    If (argCount = 0) Then
        message = "Windows Installer utility to manage language and codepage values for a package." &_
            vbNewLine & "The package language is a summary information property that designates the" &_
            vbNewLine & " primary language and any language transforms that are available, comma delim." &_
            vbNewLine & "The ProductLanguage in the database Property table is the language that is" &_
            vbNewLine & " registered for the product and determines the language used to load resources." &_
            vbNewLine & "The codepage is the ANSI codepage of the database strings, 0 if all ASCII data," &_
            vbNewLine & " and must represent the text data to avoid loss when persisting the database." &_
            vbNewLine & "The 1st argument is the path to MSI database (installer package)" &_
            vbNewLine & "To update a value, the 2nd argument contains the keyword and the 3rd the value:" &_
            vbNewLine & "   Package  {base LangId optionally followed by list of language transforms}" &_
            vbNewLine & "   Product  {LangId of the product (could be updated by language transforms)}" &_
            vbNewLine & "   Codepage {ANSI codepage of text data (use with caution when text exists!)}" &_
            vbNewLine &_
            vbNewLine & "Copyright (C) Microsoft Corporation.  All rights reserved."
        Wscript.Echo message
        Wscript.Quit 1
    End If
    
    ' Connect to Windows Installer object
    On Error Resume Next
    Dim installer : Set installer = Nothing
    Set installer = Wscript.CreateObject("WindowsInstaller.Installer") : CheckError
    
    
    ' Open database
    Dim databasePath:databasePath = Wscript.Arguments(0)
    Dim openMode : If argCount >= 3 Then openMode = msiOpenDatabaseModeTransact Else openMode = msiOpenDatabaseModeReadOnly
    Dim database : Set database = installer.OpenDatabase(databasePath, openMode) : CheckError
    
    ' Update value if supplied
    If argCount >= 3 Then
        Dim value:value = Wscript.Arguments(2)
        Select Case UCase(Wscript.Arguments(1))
            Case "PACKAGE"  : SetPackageLanguage database, value
            Case "PRODUCT"  : SetProductLanguage database, value
            Case "CODEPAGE" : SetDatabaseCodepage database, value
            Case Else       : Fail "Invalid value keyword"
        End Select
        CheckError
    End If
    
    ' Extract language info and compose report message
    Dim message:message = "Package language = "         & PackageLanguage(database) &_
                        ", ProductLanguage = " & ProductLanguage(database) &_
                        ", Database codepage = "        & DatabaseCodepage(database)
    database.Commit : CheckError  ' no effect if opened ReadOnly
    Set database = nothing
    Wscript.Echo message
    Wscript.Quit 0
    
    ' Get language list from summary information
    Function PackageLanguage(database)
        On Error Resume Next
        Dim sumInfo  : Set sumInfo = database.SummaryInformation(0) : CheckError
        Dim template : template = sumInfo.Property(7) : CheckError
        Dim iDelim:iDelim = InStr(1, template, ";", vbTextCompare)
        If iDelim = 0 Then template = "Not specified!"
        PackageLanguage = Right(template, Len(template) - iDelim)
        If Len(PackageLanguage) = 0 Then PackageLanguage = "0"
    End Function
    
    ' Get ProductLanguge property from Property table
    Function ProductLanguage(database)
        On Error Resume Next
        Dim view : Set view = database.OpenView("SELECT `Value` FROM `Property` WHERE `Property` = 'ProductLanguage'")
        view.Execute : CheckError
        Dim record : Set record = view.Fetch : CheckError
        If record Is Nothing Then ProductLanguage = "Not specified!" Else ProductLanguage = record.IntegerData(1)
    End Function
    
    ' Get ANSI codepage of database text data
    Function DatabaseCodepage(database)
        On Error Resume Next
        Dim WshShell : Set WshShell = Wscript.CreateObject("Wscript.Shell") : CheckError
        Dim tempPath:tempPath = WshShell.ExpandEnvironmentStrings("%TEMP%") : CheckError
        database.Export "_ForceCodepage", tempPath, "codepage.idt" : CheckError
        Dim fileSys : Set fileSys = CreateObject("Scripting.FileSystemObject") : CheckError
        Dim file : Set file = fileSys.OpenTextFile(tempPath & "codepage.idt", ForReading, False, TristateFalse) : CheckError
        file.ReadLine ' skip column name record
        file.ReadLine ' skip column defn record
        DatabaseCodepage = file.ReadLine
        file.Close
        Dim iDelim:iDelim = InStr(1, DatabaseCodepage, vbTab, vbTextCompare)
        If iDelim = 0 Then Fail "Failure in codepage export file"
        DatabaseCodepage = Left(DatabaseCodepage, iDelim - 1)
        fileSys.DeleteFile(tempPath & "codepage.idt")
    End Function
    
    ' Set ProductLanguge property in Property table
    Sub SetProductLanguage(database, language)
        On Error Resume Next
        If Not IsNumeric(language) Then Fail "ProductLanguage must be numeric"
        Dim view : Set view = database.OpenView("SELECT `Property`,`Value` FROM `Property`")
        view.Execute : CheckError
        Dim record : Set record = installer.CreateRecord(2)
        record.StringData(1) = "ProductLanguage"
        record.StringData(2) = CStr(language)
        view.Modify msiViewModifyAssign, record : CheckError
    End Sub
    
    ' Set ANSI codepage of database text data
    Sub SetDatabaseCodepage(database, codepage)
        On Error Resume Next
        If Not IsNumeric(codepage) Then Fail "Codepage must be numeric"
        Dim WshShell : Set WshShell = Wscript.CreateObject("Wscript.Shell") : CheckError
        Dim tempPath:tempPath = WshShell.ExpandEnvironmentStrings("%TEMP%") : CheckError
        Dim fileSys : Set fileSys = CreateObject("Scripting.FileSystemObject") : CheckError
        Dim file : Set file = fileSys.OpenTextFile(tempPath & "codepage.idt", ForWriting, True, TristateFalse) : CheckError
        file.WriteLine ' dummy column name record
        file.WriteLine ' dummy column defn record
        file.WriteLine codepage & vbTab & "_ForceCodepage"
        file.Close : CheckError
        database.Import tempPath, "codepage.idt" : CheckError
        fileSys.DeleteFile(tempPath & "codepage.idt")
    End Sub     
    
    ' Set language list in summary information
    Sub SetPackageLanguage(database, language)
        On Error Resume Next
        Dim sumInfo  : Set sumInfo = database.SummaryInformation(1) : CheckError
        Dim template : template = sumInfo.Property(7) : CheckError
        Dim iDelim:iDelim = InStr(1, template, ";", vbTextCompare)
        Dim platform : If iDelim = 0 Then platform = ";" Else platform = Left(template, iDelim)
        sumInfo.Property(7) = platform & language
        sumInfo.Persist : CheckError
    End Sub
    
    Sub CheckError
        Dim message, errRec
        If Err = 0 Then Exit Sub
        message = Err.Source & " " & Hex(Err) & ": " & Err.Description
        If Not installer Is Nothing Then
            Set errRec = installer.LastErrorRecord
            If Not errRec Is Nothing Then message = message & vbNewLine & errRec.FormatText
        End If
        Fail message
    End Sub
    
    Sub Fail(message)
        Wscript.Echo message
        Wscript.Quit 2
    End Sub
    View Code

    语法:

    WiLangId.vbs "en-usTestInstaller.msi" Package 1033,1028,2052

    执行成功会出现下面的提示框:

    这个时候,我们再点击英文的安装包。出现的是中文界面了。因为它已经根据我的系统环境自动做了选择。测试了繁体系统也是ok的。

    4.用命令选择语言。

         但是,这都还不是完整的多语言。有时候用户需要选择,比如他想再繁体系统中使用简体安装界面,而且我们的安装界面的多语言需要和我们应用程序的多语言同步起来。基于这个需求,我们还是需要在msi文件上包一个壳,先让用户去选择一门语言。那么我们怎么用命令来执行呢?

          msiexec有命令:

    msiexec /i  en-usDIAViewSetup.msi TRANSFORMS=transformszh-tw.mst

     指定在简体系统中使用繁体界面:

     

     这样在我们的C++外壳中就可以根据用户的选择也触发不同的界面。如果用户自己手动去安装,也起码可以根据他当前的系统自动切换语言。

      小结:本文没有提供完整的安装包多语言解决方法。仅仅为大家提供了一种我走通了的方法,另外还有许可证书的部分,不同的语言,不同的许可证书,但路径又是不能通过loc的方式来处理的。需要自定义窗口来处理,后面有新的发现我会继续更新。希望对苦逼的WIX学习者带来帮助。

          

  • 相关阅读:
    sql笔记
    [ACTF2020 新生赛]Include
    [极客大挑战 2019]Http
    [极客大挑战 2019]Knife
    [极客大挑战 2019]Secret File
    [HCTF 2018]admin
    [极客大挑战 2019]Havefun
    [RoarCTF 2019]Easy Calc
    ascll码转化
    《逆向工程核心原理》笔记第一章到第十一章
  • 原文地址:https://www.cnblogs.com/stoneniqiu/p/4725714.html
Copyright © 2011-2022 走看看