Index: .hgsubstate
===================================================================
--- a/.hgsubstate
+++ b/.hgsubstate
@@ -1,1 +1,1 @@
-b5c6265ab7fd840949b85100317d6b6ae13ff1c0 libadblockplus
+f1353543562ff9fca87c6478ec6f85c48622bbe0 libadblockplus
Index: README.md
===================================================================
--- a/README.md
+++ b/README.md
@@ -23,6 +23,19 @@
`msbuild` command line tool, e.g. run `msbuild /m build\ia32\adblockplus.sln`
from the Visual Studio Developer Command Prompt to create a 32 bit debug build.
+Building the installer
+----------------------
+* Execture Installer\createsolutions.bat to generate installer project files,
+this will create a bunch of project files in installer\build\ia32 and
+installer\build\x64 folders.
+* Open 'installer\build\ia32\installer.sln' and then 'installer\build\x64\installer.sln'
+in Visual Studio and build both solutions. Alternatively you can use the 'msbuild'
+command line tool, e.g. run 'msibuild /m installer\build\ia32\adblockplus.sln' and
+'msibuild /m installer\build\x64\adblockplus.sln'
+* Make sure you have InnoSetup installed. Either open and compile
+'installer\src\innosetup-exe\64BitTwoArch.iss' in InnoSetup or run
+'iscc.exe installer\src\innosetup-exe\64bitTwoArch.iss'
+
Development environment
-----------------------
Index: build_release.py
===================================================================
--- a/build_release.py
+++ b/build_release.py
@@ -18,15 +18,18 @@
key = sys.argv[2]
-def sign(*argv):
- subprocess.check_call([
- "signtool",
+def sign_command(*argv):
+ return [
+ "signtool.exe",
"sign", "/v",
"/d", "Adblock Plus",
- "/du", "http://adblockplus.org/",
+ "/du", "https://adblockplus.org/",
"/f", key,
"/tr", "http://www.startssl.com/timestamp"
- ] + list(argv))
+ ] + list(argv)
+
+def sign(*argv):
+ subprocess.check_call(sign_command(*argv))
def read_macro_value(file, macro):
handle = open(file, 'rb')
@@ -60,15 +63,9 @@
installerParams = os.environ.copy()
installerParams["VERSION"] = version
subprocess.check_call(["nmake", "/A", "ia32", "x64"], env=installerParams, cwd=os.path.join(basedir, "installer"))
-sign(os.path.join(basedir, "build", "ia32", "adblockplusie-%s-en-us-ia32.msi" % version),
- os.path.join(basedir, "build", "x64", "adblockplusie-%s-en-us-x64.msi" % version))
+sign(os.path.join(basedir, "installer", "build", "ia32", "adblockplusie-%s-multilanguage-ia32.msi" % version),
+ os.path.join(basedir, "installer", "build", "x64", "adblockplusie-%s-multilanguage-x64.msi" % version))
-subprocess.check_call(["nmake", "/A", "setup"], env=installerParams, cwd=os.path.join(basedir, "installer"))
-
-# Do the signing dance described on http://wix.sourceforge.net/manual-wix3/insignia.htm
-bundle = os.path.join(basedir, "build", "adblockplusie-%s.exe" % version)
-engine = os.path.join(basedir, "build", "engine-%s.exe" % version)
-subprocess.check_call(["insignia", "-ib", bundle, "-o", engine])
-sign(engine)
-subprocess.check_call(["insignia", "-ab", engine, bundle, "-o", bundle])
-sign(bundle)
+# If this fails, please check if InnoSetup is installed and added to you PATH
+signparam = " ".join(map(lambda p: "$q%s$q" % p if " " in p else p, sign_command("$f")))
+subprocess.check_call(["iscc", "/A", "/Ssigntool=%s" % signparam, "/Dversion=%s" % version, os.path.join(basedir, "installer", "src", "innosetup-exe", "64BitTwoArch.iss")])
Index: html/static/js/firstRun.js
===================================================================
--- a/html/static/js/firstRun.js
+++ b/html/static/js/firstRun.js
@@ -1,6 +1,6 @@
/*
* This file is part of Adblock Plus ,
- * Copyright (C) 2006-2013 Eyeo GmbH
+ * Copyright (C) 2006-2014 Eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Index: html/templates/firstRun.html
===================================================================
--- a/html/templates/firstRun.html
+++ b/html/templates/firstRun.html
@@ -2,7 +2,7 @@
-
- This setup program installs Adblock Plus for IE into your "Program Files" folder in directory "Adblock Plus for IE".
- &Install
- &Do not install
-
-
-
- Processing:
- Initializing...
- &Cancel install
-
-
-
- Adblock Plus for IE is already installed on this machine. If it's not working correctly, you may repair it. You may also uninstall it.
- &Repair
- &Uninstall
- &Do nothing
-
-
-
- &Launch
- You must restart your computer before you can use the software.
- &Restart
- &Done
-
-
-
- One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>.
- You must restart your computer to complete the rollback of the software.
- &Restart
- &Done
-
\ No newline at end of file
Index: installer/Makefile
===================================================================
--- a/installer/Makefile
+++ b/installer/Makefile
@@ -1,133 +1,138 @@
-#----------------------------------
-# Makefile for Microsoft NMAKE
-#
-# Call with only the version parameter to set the default target to all installers.
-# nmake VERSION=1.2.3
-# Call with a definition of the NMAKE variable ARCH on the command line
-# nmake VERSION=1.2.3 ARCH=ia32
-# nmake VERSION=1.2.3 ARCH=x64
-# If an architecture is specified, the default target is constrained to it.
-#----------------------------------
-
-.SUFFIXES: .msi .wixobj .wxs
-
-Build_Dir_common = ..\build\ #
-Build_Dir_ia32 = ..\build\ia32\ # comment prevents newline
-Build_Dir_x64 = ..\build\x64\ #
-
-!ifndef Configuration
-Configuration = Release
-!endif
-
-!ifndef VERSION
-VERSION = 99.9
-!endif
-
-#---------------------
-# Default Targets
-#
-# We change the default rule depending upon the ARCH (architecture) definition.
-#---------------------
-
-Installer_ia32 = $(Build_Dir_ia32)adblockplusie-$(VERSION)-en-us-ia32.msi
-Installer_x64 = $(Build_Dir_x64)adblockplusie-$(VERSION)-en-us-x64.msi
-Setup = $(Build_Dir_common)adblockplusie-$(VERSION).exe
-
-!ifndef ARCH
-default: $(Installer_ia32) $(Installer_x64)
-!elseif "$(ARCH)"=="ia32"
-default: $(Installer_ia32)
-!elseif "$(ARCH)"=="x64"
-default: $(Installer_x64)
-!else
-!error Unknown variable ARCH=$(ARCH)
-!endif
-
-default:
- @echo Available targets
- @echo - ia32
- @echo - x64
- @echo - setup
- @echo - install-ia32
- @echo - install-x64
-
-all: ia32 x64 setup
-
-ia32: $(Installer_ia32)
-
-x64: $(Installer_x64)
-
-setup: $(Setup)
-
-#---------------------
-# candle .wxs --> .wixobj
-#---------------------
-
-Candle = candle -nologo -dNoDefault -dVersion=$(VERSION) "-dConfiguration=$(Configuration)" $(CANDLE_FLAGS) $** -out $@
-
-objects_common = $(Build_Dir_common)custom_WixUI_InstallDir.wixobj
-$(objects_common): custom_WixUI_InstallDir.wxs
- $(Candle)
-
-objects_ia32 = $(Build_Dir_ia32)adblockplusie-$(VERSION).wixobj
-$(objects_ia32): adblockplusie.wxs
- $(Candle) -arch x86
-
-objects_x64 = $(Build_Dir_x64)adblockplusie-$(VERSION).wixobj
-$(objects_x64): adblockplusie.wxs
- $(Candle) -arch x64
-
-object_setup = $(Build_Dir_common)setup-$(VERSION).wixobj
-$(object_setup): setup.wxs
- $(Candle) -ext WixBalExtension
-
-#---------------------
-# light .wixobj --> .msi
-#---------------------
-
-Light = light -notidy -nologo -cultures:en-us -ext WixUIExtension -out $@
-
-$(Installer_ia32): $(objects_ia32) $(objects_common) "..\build\ia32\$(Configuration)\AdblockPlus.dll" "build\ia32\Debug\installer-ca.dll"
- $(Light) $(objects_ia32) $(objects_common) -loc en-us.wxl -sval
-
-# TODO: Don't ignore errors here (fix install paths)
-$(Installer_x64): $(objects_x64) $(objects_common) "..\build\ia32\$(Configuration)\AdblockPlus.dll" "..\build\x64\$(Configuration)\AdblockPlus.dll"
- -$(Light) $(objects_x64) $(objects_common) -loc en-us.wxl
-
-#---------------------
-# light .wixobj --> .exe
-#---------------------
-
-$(Setup): $(object_setup) bootstrap-theme.xml bootstrap-theme.wxl
- $(Light) $(object_setup) -ext WixBalExtension -loc bootstrap-theme.wxl
-
-#---------------------
-# msiexec .msi --> installed --> uninstalled
-#---------------------
-
-install-ia32: $(Installer_ia32)
- call < installed --> uninstalled
+#---------------------
+
+uninstall:
+ msiexec /x {4f27c814-5ee0-4b25-b3ab-3ad565551918}
+
+install-setup: $(Setup)
+ $(Setup) -log ..\build\setup.log
+
+#---------------------
+# Self-extracting versions of the MSI
+#---------------------
+
+#
+# 7-Zip can't help but store directories (no command line switch to do otherwise).
+#
+$(Installer_ia32_exe): $(Installer_ia32_msi) $(Source_Dir_setup)self-extract-config-ia32.txt
+ cd $(Build_Dir_ia32) & 7z a -t7z $(Installer_ia32_name).7z ..\..\$(MSBuild_Output_ia32)
+ copy /b $(Source_Dir_setup)7zS.sfx + $(Source_Dir_setup)self-extract-config-ia32.txt + $(Installer_ia32_archive) $(Installer_ia32_exe)
+
+$(Installer_x64_exe): $(Installer_x64_msi) $(Source_Dir_setup)self-extract-config-x64.txt
+ cd $(Build_Dir_x64) & 7z a -t7z $(Installer_x64_name).7z ..\..\$(MSBuild_Output_x64)
+ copy /b $(Source_Dir_setup)7zS.sfx + $(Source_Dir_setup)self-extract-config-x64.txt + $(Installer_x64_archive) $(Installer_x64_exe)
+
Index: installer/createsolutions.bat
===================================================================
rename from installer/createsolution.bat
rename to installer/createsolutions.bat
Index: installer/emb.vbs
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/emb.vbs
@@ -0,0 +1,40 @@
+' Emb.vbs.
+' Argument(0) is the name of the storage.
+' Argument(1) is the original database.
+' Argument(2) is the path to the transform file.
+' This was changed from the original to work around a gyp defect involved that rewrites whole command lines rather than just path-containing variables.
+
+Option Explicit
+
+' Check arguments
+If WScript.Arguments.Count < 2 Then
+ WScript.Echo "Usage is emb.vbs [storage name] [original database] [transform]"
+ 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")
+
+' Evaluate command-line arguments and set open and update modes
+Dim databasePath: databasePath = Wscript.Arguments(1)
+Dim importPath : importPath = Wscript.Arguments(2)
+Dim storageName : storageName = Wscript.Arguments(0)
+
+' Open database and create a view on the _Storages table
+Dim sqlQuery : sqlQuery = "SELECT `Name`,`Data` FROM _Storages"
+Dim database : Set database = installer.OpenDatabase(databasePath, 1)
+Dim view : Set view = database.OpenView(sqlQuery)
+
+'Create and Insert the row.
+Dim record : Set record = installer.CreateRecord(2)
+record.StringData(1) = storageName
+view.Execute record
+
+'Insert storage - copy data into stream
+record.SetStream 2, importPath
+view.Modify 3, record
+database.Commit
+Set view = Nothing
+Set database = Nothing
\ No newline at end of file
Index: installer/en-us.wxl
===================================================================
deleted file mode 100644
--- a/installer/en-us.wxl
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
- 1033
- 1252
-
- Adblock Plus for IE (32-bit)
- Adblock Plus for IE (32-bit and 64-bit)
- Installer
- Adblock Plus for IE
-
- A newer version of Adblock Plus for IE is already installed.
- Adblock Plus for IE
- All of Adblock Plus for IE
-
Index: installer/googletest.gyp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/googletest.gyp
@@ -0,0 +1,91 @@
+#
+# Mostly a copy of googletest.gyp from the project 'libadblockplus'.
+# Copied here because it needed modifications to compile without error.
+# Because the original is in another project, changing it there would require testing that projects and all others included by it.
+#
+{
+ 'variables':
+ {
+ 'googletest': '../libadblockplus/third_party/googletest'
+ },
+ 'target_defaults':
+ {
+ 'configurations':
+ {
+ 'Debug':
+ {
+ 'defines': [ 'DEBUG' ],
+ 'msvs_settings':
+ {
+ 'VCCLCompilerTool':
+ {
+ 'Optimization': '0',
+ 'RuntimeLibrary': '1', # /MTd
+ },
+ },
+ },
+ 'Release':
+ {
+ 'msvs_settings':
+ {
+ 'VCCLCompilerTool':
+ {
+ 'Optimization': '2',
+ 'InlineFunctionExpansion': '2',
+ 'EnableIntrinsicFunctions': 'true',
+ 'FavorSizeOrSpeed': '0',
+ 'StringPooling': 'true',
+ 'RuntimeLibrary': '0', # /MT
+ },
+ },
+ },
+ },
+ 'msvs_configuration_attributes':
+ {
+ 'OutputDirectory': '<(DEPTH)\\$(ConfigurationName)',
+ 'IntermediateDirectory': '$(OutDir)\\obj\\$(ProjectName)',
+ },
+ 'conditions':
+ [[
+ 'target_arch=="x64"',
+ {
+ 'msvs_configuration_platform': 'x64',
+ },
+ ]]
+ },
+ 'targets':
+ [{
+ 'target_name': 'googletest',
+ 'type': 'static_library',
+ 'sources': [
+ '<(googletest)/src/gtest-death-test.cc',
+ '<(googletest)/src/gtest-filepath.cc',
+ '<(googletest)/src/gtest-port.cc',
+ '<(googletest)/src/gtest-printers.cc',
+ '<(googletest)/src/gtest-test-part.cc',
+ '<(googletest)/src/gtest-typed-test.cc',
+ '<(googletest)/src/gtest.cc'
+ ],
+ 'include_dirs': [ '<(googletest)', '<(googletest)/include' ],
+ 'direct_dependent_settings':
+ {
+ 'include_dirs': [ '<(googletest)', '<(googletest)/include' ]
+ },
+ 'defines': [ '_VARIADIC_MAX=10' ],
+ 'direct_dependent_settings':
+ {
+ 'defines': [ '_VARIADIC_MAX=10' ],
+ },
+ },{
+ 'target_name': 'googletest_main',
+ 'type': 'static_library',
+ 'sources': ['<(googletest)/src/gtest_main.cc'],
+ 'include_dirs': [ '<(googletest)/include' ],
+ 'dependencies': [ 'googletest' ],
+ 'direct_dependent_settings':
+ {
+ 'defines': [ '_VARIADIC_MAX=10' ],
+ 'include_dirs': [ '<(googletest)/include' ],
+ }
+ }]
+}
Index: installer/install.cmd
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/install.cmd
@@ -0,0 +1,65 @@
+@echo off
+setlocal
+set PACKAGE=%1
+if "%PACKAGE%"=="-h" goto Help
+if "%PACKAGE%"=="--help" goto Help
+if "%PACKAGE%"=="help" goto Help
+
+:ArgArch
+shift
+set ARCH=%1
+if "%ARCH%"=="ia32" goto ArgArchEnd
+if "%ARCH%"=="x64" goto ArgArchEnd
+if NOT "%ARCH%"=="" goto ArgArchError
+set ARCH=x64
+goto ArgArchEnd
+:ArgArchError
+echo Unrecognized architecture argument '%ARCH%' 1>&2
+exit /b 1
+:ArgArchEnd
+
+:ArgPackage
+if NOT "%PACKAGE%"=="abp" goto ArgPackage1
+set MSI=adblockplusie-FINAL-%ARCH%.msi
+set LOG=install-adblockplusie.log
+goto GoMsiexec
+:ArgPackage1
+if NOT "%PACKAGE%"=="test" goto ArgPackage2
+set MSI=test-installer-lib.msi
+set LOG=install-test.log
+goto GoMsiexec
+:ArgPackage2
+if NOT "%PACKAGE%"=="setup" goto ArgPackageError
+goto goSetup
+:ArgPackageError
+echo Unrecognized package argument '%PACKAGE%' 1>&2
+exit /b 1
+
+:GoMsiexec
+pushd %~dp0%
+cd build\%ARCH%
+echo on
+msiexec /i %MSI% /l*v %LOG%
+@echo off
+popd
+exit /b
+
+:GoSetup
+pushd %~dp0%
+cd build\common
+echo on
+echo .\setup-abp-ie
+@echo off
+popd
+exit /b
+
+:Help
+echo install - install ABP-IE from the build directory
+echo.
+echo usage: install [-h] ^ ^
+echo package - either 'abp' or 'test'
+echo architecture - either 'ia32' or 'x64'
+echo ^-h ^| --help - show this help message only
+echo.
+exit /b
+
Index: installer/installer.gyp
===================================================================
--- a/installer/installer.gyp
+++ b/installer/installer.gyp
@@ -1,65 +1,849 @@
-{
- 'includes': [ '../defaults.gypi' ],
-
- 'variables':
- {
- # The 'component' variable is required to use 'defaults.gypi'.
- # It's value 'shared_library' is duplicated by the 'type' property of a target.
- # We may want to migrate compiler settings for the CA library at some point and stop using 'defaults.gypi' here
- 'component%': '',
- },
-
- 'target_defaults':
- {
- 'msvs_cygwin_shell': 0,
- },
-
- 'targets':
- [
- #############
- # Custom Action library for the installer
- #############
- {
- 'target_name': 'installer-ca',
- 'type': 'shared_library',
- 'component': 'shared_library',
- 'sources':
- [
- #
- # Custom Action
- #
- 'src/custom-action/abp_ca.cpp',
- 'src/custom-action/abp_ca.def',
- 'src/custom-action/abp_ca.rc',
- 'src/custom-action/close_application.cpp',
- #
- # Windows Installer library
- #
- 'src/installer-lib/database.cpp',
- 'src/installer-lib/database.h',
- 'src/installer-lib/DLL.cpp',
- 'src/installer-lib/DLL.h',
- 'src/installer-lib/interaction.cpp',
- 'src/installer-lib/interaction.h',
- 'src/installer-lib/property.cpp',
- 'src/installer-lib/property.h',
- 'src/installer-lib/record.cpp',
- 'src/installer-lib/record.h',
- 'src/installer-lib/session.cpp',
- 'src/installer-lib/session.h',
- ],
- 'include_dirs':
- [
- 'src/installer-lib',
- ],
- 'link_settings':
- {
- 'libraries': [ 'user32.lib', 'Shell32.lib', 'advapi32.lib', 'msi.lib', 'Version.lib' ]
- },
- 'msvs_settings':
- {
- 'VCLinkerTool': {}
- }
- },
- ]
-}
+#
+# Expects command line definition for variable 'target_arch'
+# Must be either 'ia32' or 'x64'
+#
+# This .gyp file sits in directory 'installer'.
+# When gyp translates files locations, base them here.
+#
+# The solution file from this .gyp source lands in 'installer/build/<(target_arch)'.
+# When gyp does not translate file locations, base them here.
+#
+{
+ 'includes': [ '../defaults.gypi' ],
+ 'variables':
+ {
+ #
+ # Architecture specification
+ # -- WiX uses {x86,x64}. VS uses {ia32,x64}
+ #
+ 'conditions' :
+ [
+ [ 'target_arch=="ia32"', { 'candle_arch': 'x86' } ],
+ [ 'target_arch=="x64"', { 'candle_arch': 'x64' } ]
+ ],
+
+ #
+ # Build directories, both common and architecture-specific
+ #
+ 'build_dir_arch': 'build/<(target_arch)',
+ 'build_dir_common': 'build/common',
+
+ #
+ # MSI file names.
+ # -- The base MSI is a single-language MSI as originally constructed.
+ # This is the one from which all transforms are derived.
+ # -- The interim MSI is the working copy of the multilanguage MSI during the build process.
+ # It starts off as a copy of the base MSI.
+ # Transforms are added to the MSI one at a time.
+ # -- The final MSI is the ultimate product of the build.
+ # It is simply the last interim MSI, after all the transforms have been embedded.
+ #
+ 'base_msi': '<(build_dir_arch)/adblockplusie-BASE-<(target_arch).msi',
+ 'interim_msi': '<(build_dir_arch)/adblockplusie-INTERIM-<(target_arch).msi',
+ 'final_msi': '<(build_dir_arch)/adbblockplusie-multilanguage-<(target_arch).msi',
+
+ #
+ # WiX installer sources for the compiler, architecture-specific.
+ # The top source is what goes on the command line.
+ # All the sources are inputs.
+ # Note that locality sources (.wxl) are not present here because they're handled at link time.
+ #
+ 'installer_source_top_file': 'src/msi/adblockplusie.wxs',
+ 'installer_source_files':
+ [
+ '<(installer_source_top_file)',
+ 'src/msi/bho_registry_value.wxi',
+ 'src/msi/dll_class.wxi',
+ ],
+ 'installer_object_file': '<(build_dir_arch)/adblockplusie.wixobj',
+
+ #
+ # WiX installer sources for the compiler, common to all architectures
+ #
+ 'common_source_files': [ 'src/msi/custom_WixUI_InstallDir.wxs' ],
+ 'common_object_file': '<(build_dir_common)/common.wixobj',
+
+ #
+ # All the assets from the plug-in that are copied into the MSI file.
+ #
+ 'payloads': [],
+
+ # The 'component' variable is required to use 'defaults.gypi'.
+ # It's value 'shared_library' is duplicated by the 'type' property of a target.
+ # We may want to migrate compiler settings for the CA library at some point and stop using 'defaults.gypi' here
+ 'component%': '',
+ },
+
+ 'target_defaults':
+ {
+ 'msvs_cygwin_shell': 0,
+ 'variables': {
+ #
+ # We don't really want a default 'locale_id', but we need one to avoid an "undefined variable" error when the ".wxl" rule is invoked.
+ # Note that the action in the rule uses later-phase substitution with ">", which occurs after the rule is merged with the target.
+ # Apparently, though, it's also being evaluated earlier, before 'locale_id' is defined in the target.
+ # Therefore, count this as a workaround for a gyp defect.
+ #
+ 'locale_id%': '0',
+
+ #
+ # We do want a default 'msi_build_phase', because in all but the first MSI build we want the flag "additional"
+ #
+ 'msi_build_phase%': 'additional',
+ },
+ 'rules':
+ [ {
+ #
+ # Rule to build a single-language MSI as part of a chain to create a multiple-language MSI
+ # The rule runs a .cmd file to execute the commands; this chose arises from gyp limitations and defects.
+ #
+ # gyp can only handle a single rule per extension.
+ # Since we have only one ".wxl" file, we need to run all the operations (link MSI, diff to MST, embed MST into MSI) with a single action.
+ # gyp does not have syntax for multi-line actions.
+ # Including a newline as a token doesn't work because of the way gyp "fixes" path names; it treats the newline as a path, prefixes it, and quotes it all.
+ #
+ # Furthermore, there's the issue of overriding the rule for the first MSI, the one that generates the BASE against which transforms are generated.
+ # In order to override the rule, we'd need to duplicate most of this one, particularly all the file name expressions, violating the write-once principle.
+ #
+ 'rule_name': 'MSI Build',
+ 'extension': 'wxl',
+ 'message': 'Generating embedded transform for "<(RULE_INPUT_ROOT)"',
+ 'inputs': [ 'emb.vbs', '<(base_msi)', '<@(payloads)' ],
+ 'outputs': [ '<(build_dir_arch)/adblockplusie-<(RULE_INPUT_ROOT)-<(target_arch).msi', '<(build_dir_arch)/adblockplusie-<(RULE_INPUT_ROOT)-<(target_arch).mst' ],
+ 'action':
+ [
+ '..\..\msibuild.cmd >(msi_build_phase) >(locale_id) >(RULE_INPUT_ROOT)', '<(RULE_INPUT_PATH)',
+ '<(build_dir_arch)/adblockplusie-<(RULE_INPUT_ROOT)-<(target_arch).msi',
+ '<(build_dir_arch)/adblockplusie-<(RULE_INPUT_ROOT)-<(target_arch).mst',
+ '<(build_dir_arch)/adblockplusie-BASE-<(target_arch).msi',
+ '<(build_dir_arch)/adblockplusie-INTERIM-<(target_arch).msi',
+ '<(installer_object_file)', '<(common_object_file)',
+ ]
+ } ],
+ },
+
+ 'targets':
+ [
+ #############
+ # Compile common WiX source.
+ # All the WiX-linked sources that depend neither on architecture nor configuration.
+ # Principally for user interface.
+ #############
+ {
+ 'target_name': 'Installer, common WiX',
+ 'type': 'none',
+ 'actions':
+ [ {
+ 'action_name': 'WiX compile common',
+ 'message': 'Compiling common WiX sources',
+ 'inputs':
+ [
+ '<@(common_source_files)'
+ ],
+ 'outputs':
+ [
+ # List must contain only a single element so that "-out" argument works correctly.
+ '<(common_object_file)'
+ ],
+ 'action':
+ [ 'candle -nologo -dNoDefault ', '-out', '<@(_outputs)', '<@(_inputs)' ]
+ } ]
+ },
+
+ #############
+ # Compile installer WiX source.
+ # Platform-specific.
+ #############
+ {
+ 'target_name': 'Installer, architecture-specific WiX',
+ 'type': 'none',
+ 'actions':
+ [ {
+ 'action_name': 'Compile WiX installer',
+ 'message': 'Compiling installer WiX sources',
+ 'inputs':
+ [
+ '<@(installer_source_files)'
+ ],
+ 'outputs':
+ [
+ # List must contain only a single element so that "-out" argument works correctly.
+ '<(installer_object_file)'
+ ],
+ 'action':
+ [ 'candle -nologo -arch <(candle_arch) -dNoDefault -dVersion=91.0 -dConfiguration=Release', '-out', '<@(_outputs)', '<(installer_source_top_file)' ]
+ } ]
+ },
+
+ ##################################
+ # MSI targets
+ #
+ # Building a multiple-language MSI requires embedding a transform for each language into a single MSI database.
+ # Each step requires a locale identifier (Microsoft LCID) as a parameter and a WiX localization file (.wxl) as a source.
+ # gyp does not support per-source-file parameters, so we're stuck with one project per step.
+ # The naming convention for projects:
+ # - The token "MSI". Projects appear in dictionary order in the resulting solution file. A common initial token groups them.
+ # - The language tag. These are mostly just the two-letter language codes. There are a few sublanguage tags, though.
+ # - The LCID of the language in four digit hexadecimal form, zero-padded if necessary.
+ # Note: This supports Traditional Chinese (used in Taiwan) with LCID 0x7C04.
+ # Exception: The BASE MSI is named so that it appears first.
+ #
+ # These steps are arranged as a linked list, starting with the BASE version of the MSI,
+ # which is simply a single-language MSI whose language will be the default language for the final installer.
+ # The list is singly-linked by the 'dependencies' element.
+ # For sanity (and code audit), the project declarations appear below in the same order as they will appear in the compiled solution file.
+ #
+ # The naming convention for WiX localization files (.wxl) is a combination of the language ID and the sublanguage ID.
+ # It can be thought of as a text representation of the LCID as the Windows API sees it.
+ # Mostly these agree with IETF-style two-part identifiers, but they're not always the same.
+ # For many languages, there is only a single sublanguage defined; these languages still use a two-part name for consistency.
+ # Generic languages, that is, those with no sublanguage ID (it's zero), use names of only a single part.
+ # The generic languages currently used are these: ar, de, en, fr, it, ms, nl.
+ # We currently don't have generic "es" (Spanish), nor a specific "es-MX" (Spanish - Mexico).
+ #
+ # Adding a new language consists of three steps.
+ # 1. Create a '.wxl' localization file in 'src\msi\locale'.
+ # This file contains an XML element that specifies the LCID.
+ # Set the codepage element also, if needed.
+ # 2. In the element inside 'adblockplusie.wxs', the attribute "languages" contains a comma-separated list of all languages supported by the installer.
+ # Add the LCID to this list.
+ # If this isn't done, the build will succeed but the embedded transform will be ignored at run-time.
+ # 3. Create a target for the language.
+ # Define the gyp variable 'locale_id' as the LCID.
+ # Add the target to the linked project list by setting the gyp variable 'dependencies' of both the new project and the one following it.
+ #
+ # Reference: MSDN "Language Identifier Constants and Strings" http://msdn.microsoft.com/en-us/library/dd318693%28v=vs.85%29.aspx
+ # Many languages have only a single sublanguage. For these, we use the sublanguage-specific LCID (usually starting with 0x04).
+ # For languages with more than one sublanguage (English, German, French, etc.), we use the generic LCID (sublanguage code equals zero).
+ # Exception: Spanish is currently es-ES, and the sublanguage-specific LCID is used.
+ #
+ # We use Alpha-3 codes (from ISO 693-2): fil.
+ # Filipino (fil) doesn't have a two-letter code.
+ #
+ # Warning: The Windows Installer still (as of 2013) does not fully support Unicode.
+ # Strings remain encoded by code page specification.
+ # Certain languages are Unicode-only, such as Hindi (hi), do not have code page assignments.
+ # Such languages might not work if localized, either partially or completely.
+ # These languages _need_ testing before publication, not just a wish and a prayer.
+ #
+ # Warning Continued: The issue is that Win32 ANSI entry points (those ending in "A") will fail for such languages.
+ # Wide-character entry points (those ending in "W") should work.
+ # The issue is that much of the interior of the installer is opaque, and it's not possible to know if any ANSI calls remain enabled.
+ # The WiX user interface code (generally) uses wide characters, but it calls some default error notifications that may not behave correctly.
+ #
+ # Another warning, the .wxl files are all XML files, whose declared encoding is "utf-8".
+ # This encoding may need to be changed for certain files to ensure that character input is correct.
+ #
+ # Reference: MSDN "Code Page Identifiers" http://msdn.microsoft.com/en-us/library/dd317756%28VS.85%29.aspx
+ #
+ ##################################
+ #############
+ # Link WiX objects and payloads, creating base MSI.
+ # Platform-specific.
+ # Generates the reference MSI upon which all transforms are based.
+ #############
+ {
+ 'target_name': 'MSI @ en 9 (English) [BASE]',
+ 'type': 'none',
+ 'dependencies' :
+ [
+ 'Installer, architecture-specific WiX',
+ 'Installer, common WiX',
+ 'installer-ca'
+ ],
+ 'variables': {
+ # Only define 'msi_build_phase' once as 'initial', here in the BASE target. All others use the default value.
+ 'msi_build_phase': 'initial',
+ 'locale_id': '9',
+ },
+ 'sources': [ 'src/msi/locale/en.wxl' ],
+ },
+
+ #############
+ # MSI ar 1 (Arabic - Saudi Arabia)
+ #############
+ {
+ 'target_name': 'MSI ar 1 (Arabic - Saudi Arabia)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI @ en 9 (English) [BASE]' ],
+ 'variables': { 'locale_id': '1' },
+ 'sources': [ 'src/msi/locale/ar-SA.wxl' ],
+ },
+
+ #############
+ # MSI bg-BG 1026 (Bulgarian - Bulgaria)
+ #############
+ {
+ 'target_name': 'MSI bg-BG 1026 (Bulgarian - Bulgaria)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI ar 1 (Arabic - Saudi Arabia)' ],
+ 'variables': { 'locale_id': '1026' },
+ 'sources': [ 'src/msi/locale/bg-BG.wxl' ],
+ },
+
+ #############
+ # MSI ca-ES 1027 (Catalan - Spain)
+ #############
+ {
+ 'target_name': 'MSI ca-ES 1027 (Catalan - Spain)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI bg-BG 1026 (Bulgarian - Bulgaria)' ],
+ 'variables': { 'locale_id': '1027' },
+ 'sources': [ 'src/msi/locale/ca-ES.wxl' ],
+ },
+
+ #############
+ # MSI cs-CZ 1029 (Czech - Czech Republic)
+ #############
+ {
+ 'target_name': 'MSI cs-CZ 1029 (Czech - Czech Republic)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI ca-ES 1027 (Catalan - Spain)' ],
+ 'variables': { 'locale_id': '1029' },
+ 'sources': [ 'src/msi/locale/cs-CZ.wxl' ],
+ },
+
+ #############
+ # MSI da-DK 1030 (Danish - Denmark)
+ #############
+ {
+ 'target_name': 'MSI da-DK 1030 (Danish - Denmark)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI cs-CZ 1029 (Czech - Czech Republic)' ],
+ 'variables': { 'locale_id': '1030' },
+ 'sources': [ 'src/msi/locale/da-DK.wxl' ],
+ },
+
+ #############
+ # MSI el-GR 1032 (Greek - Greece)
+ #############
+ {
+ 'target_name': 'MSI el-GR 1032 (Greek - Greece)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI da-DK 1030 (Danish - Denmark)' ],
+ 'variables': { 'locale_id': '1032' },
+ 'sources': [ 'src/msi/locale/el-GR.wxl' ],
+ },
+
+ #############
+ # MSI de 7 (German - Germany)
+ #############
+ {
+ 'target_name': 'MSI de 7 (German - Germany)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI el-GR 1032 (Greek - Greece)' ],
+ 'variables': { 'locale_id': '7' },
+ 'sources': [ 'src/msi/locale/de-DE.wxl' ],
+ },
+
+ #############
+ # MSI es-ES 1034 (Spanish - Spain)
+ #############
+ {
+ 'target_name': 'MSI es-ES 1034 (Spanish - Spain)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI de 7 (German - Germany)' ],
+ 'variables': { 'locale_id': '1034' },
+ 'sources': [ 'src/msi/locale/es-ES.wxl' ],
+ },
+
+ #############
+ # MSI et-EE 1061 (Estonian - Estonia)
+ #############
+ {
+ 'target_name': 'MSI et-EE 1061 (Estonian - Estonia)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI es-ES 1034 (Spanish - Spain)' ],
+ 'variables': { 'locale_id': '1061' },
+ 'sources': [ 'src/msi/locale/et-EE.wxl' ],
+ },
+
+ #############
+ # MSI fi 1035 (Finnish - Finland)
+ #############
+ {
+ 'target_name': 'MSI fi 1035 (Finnish - Finland)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI et-EE 1061 (Estonian - Estonia)' ],
+ 'variables': { 'locale_id': '1035' },
+ 'sources': [ 'src/msi/locale/fi-FI.wxl' ],
+ },
+
+ #############
+ # MSI fr 12 (French - France)
+ #############
+ {
+ 'target_name': 'MSI fr 12 (French - France)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI fi 1035 (Finnish - Finland)' ],
+ 'variables': { 'locale_id': '12' },
+ 'sources': [ 'src/msi/locale/fr-FR.wxl' ],
+ },
+
+ #############
+ # MSI he-IL 1037 (Hebrew - Israel)
+ #############
+ {
+ 'target_name': 'MSI he-IL 1037 (Hebrew - Israel)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI fr 12 (French - France)' ],
+ 'variables': { 'locale_id': '1037' },
+ 'sources': [ 'src/msi/locale/he-IL.wxl' ],
+ },
+
+ #############
+ # MSI hr-HR 1050 (Croatian - Croatia)
+ #############
+ {
+ 'target_name': 'MSI hr-HR 1050 (Croatian - Croatia)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI he-IL 1037 (Hebrew - Israel)' ],
+ 'variables': { 'locale_id': '1050' },
+ 'sources': [ 'src/msi/locale/hr-HR.wxl' ],
+ },
+
+ #############
+ # MSI hu-HU 1038 (Hungarian - Hungary)
+ #############
+ {
+ 'target_name': 'MSI hu-HU 1038 (Hungarian - Hungary)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI hr-HR 1050 (Croatian - Croatia)' ],
+ 'variables': { 'locale_id': '1038' },
+ 'sources': [ 'src/msi/locale/hu-HU.wxl' ],
+ },
+
+ #############
+ # MSI it 16 (Italian - Italy)
+ #############
+ {
+ 'target_name': 'MSI it 16 (Italian - Italy)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI hu-HU 1038 (Hungarian - Hungary)' ],
+ 'variables': { 'locale_id': '16' },
+ 'sources': [ 'src/msi/locale/it-IT.wxl' ],
+ },
+
+ #############
+ # MSI ja-JP 1041 (Japanese - Japan)
+ #############
+ {
+ 'target_name': 'MSI ja-JP 1041 (Japanese - Japan)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI it 16 (Italian - Italy)' ],
+ 'variables': { 'locale_id': '1041' },
+ 'sources': [ 'src/msi/locale/ja-JP.wxl' ],
+ },
+
+ #############
+ # MSI nb-NO 1044 (Norwegian - Bokmål, Norway)
+ # Target name has a vowel change to work around a character encoding problem in gyp/MSVS.
+ #############
+ {
+ 'target_name': 'MSI nb-NO 1044 (Norwegian - Bokmal, Norway)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI ja-JP 1041 (Japanese - Japan)' ],
+ 'variables': { 'locale_id': '1044' },
+ 'sources': [ 'src/msi/locale/nb-NO.wxl' ],
+ },
+
+ #############
+ # MSI nl 19 (Dutch - Netherlands)
+ #############
+ {
+ 'target_name': 'MSI nl 19 (Dutch - Netherlands)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI nb-NO 1044 (Norwegian - Bokmal, Norway)' ],
+ 'variables': { 'locale_id': '19' },
+ 'sources': [ 'src/msi/locale/nl-NL.wxl' ],
+ },
+
+ #############
+ # MSI pl-PL 1045 (Polish - Poland)
+ #############
+ {
+ 'target_name': 'MSI pl-PL 1045 (Polish - Poland)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI nl 19 (Dutch - Netherlands)' ],
+ 'variables': { 'locale_id': '1045' },
+ 'sources': [ 'src/msi/locale/pl-PL.wxl' ],
+ },
+
+ #############
+ # MSI pt-BR 1046 (Portuguese - Brazil)
+ #############
+ {
+ 'target_name': 'MSI pt-BR 1046 (Portuguese - Brazil)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI pl-PL 1045 (Polish - Poland)' ],
+ 'variables': { 'locale_id': '1046' },
+ 'sources': [ 'src/msi/locale/pt-BR.wxl' ],
+ },
+
+ #############
+ # MSI pt-PT 2070 (Portuguese - Portugal)
+ #############
+ {
+ 'target_name': 'MSI pt-PT 2070 (Portuguese - Portugal)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI pt-BR 1046 (Portuguese - Brazil)' ],
+ 'variables': { 'locale_id': '2070' },
+ 'sources': [ 'src/msi/locale/pt-PT.wxl' ],
+ },
+
+ #############
+ # MSI ro-RO 1048 (Romanian - Romania)
+ #############
+ {
+ 'target_name': 'MSI ro-RO 1048 (Romanian - Romania)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI pt-PT 2070 (Portuguese - Portugal)' ],
+ 'variables': { 'locale_id': '1048' },
+ 'sources': [ 'src/msi/locale/ro-RO.wxl' ],
+ },
+
+ #############
+ # MSI ru-RU 1049 (Russian - Russia)
+ #############
+ {
+ 'target_name': 'MSI ru-RU 1049 (Russian - Russia)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI ro-RO 1048 (Romanian - Romania)' ],
+ 'variables': { 'locale_id': '1049' },
+ 'sources': [ 'src/msi/locale/ru-RU.wxl' ],
+ },
+
+ #############
+ # MSI sk-SK 1051 (Slovak - Slovakia)
+ #############
+ {
+ 'target_name': 'MSI sk-SK 1051 (Slovak - Slovakia)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI ru-RU 1049 (Russian - Russia)' ],
+ 'variables': { 'locale_id': '1051' },
+ 'sources': [ 'src/msi/locale/sk-SK.wxl' ],
+ },
+
+ #############
+ # MSI sv-SE 1053 (Swedish - Sweden)
+ #############
+ {
+ 'target_name': 'MSI sv-SE 1053 (Swedish - Sweden)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI sk-SK 1051 (Slovak - Slovakia)' ],
+ 'variables': { 'locale_id': '1053' },
+ 'sources': [ 'src/msi/locale/sv-SE.wxl' ],
+ },
+
+ #############
+ # MSI th-TH 1054 (Thai - Thailand)
+ #############
+ {
+ 'target_name': 'MSI th-TH 1054 (Thai - Thailand)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI sv-SE 1053 (Swedish - Sweden)' ],
+ 'variables': { 'locale_id': '1054' },
+ 'sources': [ 'src/msi/locale/th-TH.wxl' ],
+ },
+
+ #############
+ # MSI tr-TR 1055 (Turkish - Turkey)
+ #############
+ {
+ 'target_name': 'MSI tr-TR 1055 (Turkish - Turkey)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI th-TH 1054 (Thai - Thailand)' ],
+ 'variables': { 'locale_id': '1055' },
+ 'sources': [ 'src/msi/locale/tr-TR.wxl' ],
+ },
+
+ #############
+ # MSI uk-UA 1058 (Ukrainian - Ukraine)
+ #############
+ {
+ 'target_name': 'MSI uk-UA 1058 (Ukrainian - Ukraine)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI tr-TR 1055 (Turkish - Turkey)' ],
+ 'variables': { 'locale_id': '1058' },
+ 'sources': [ 'src/msi/locale/uk-UA.wxl' ],
+ },
+
+ #####################
+ # Note: The locale codes for Chinese differ between the usage in the .NET library and the Windows OS.
+ # Mostly these are the same, but there are some places where LCID's are listed that use the .NET values.
+ # The Windows Installer is a laggard in i18n issues, so we're taking the precautionary approach to use Windows API values for the LCID.
+ # The .NET version has the notion of culture hierarchies, an invariant culture, and a neutral culture.
+ # The .NET neutral culture ID for Traditional Chinese is 0x7C04, but this is not supported in the Windows API.
+ # The .NET neutral culture ID for Simplified Chinese 0x0004, but this is the neutral/invariant LCID for the Windows API.
+ # As a result, we're using sublanguage codes 0x01 and 0x02 for Taiwan and China, respectively, in the LCID's below.
+ #####################
+ #############
+ # MSI zh-CN 2052 (Chinese - China)
+ #############
+ {
+ 'target_name': 'MSI zh-CN 2052 (Chinese - China)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI uk-UA 1058 (Ukrainian - Ukraine)' ],
+ 'variables': { 'locale_id': '2052' },
+ 'sources': [ 'src/msi/locale/zh-CN.wxl' ],
+ },
+
+ #############
+ # MSI zh-TW 1028 (Chinese - Taiwan)
+ #############
+ {
+ 'target_name': 'MSI zh-TW 1028 (Chinese - Taiwan)',
+ 'type': 'none',
+ 'dependencies' : [ 'MSI zh-CN 2052 (Chinese - China)' ],
+ 'variables': { 'locale_id': '1028' },
+ 'sources': [ 'src/msi/locale/zh-TW.wxl' ],
+ },
+
+ #####################
+ # The last step is to copy the INTERIM file to the FINAL file
+ #####################
+ #############
+ # MSI Final
+ #############
+ {
+ 'target_name': 'MSI _ [FINAL]',
+ 'type': 'none',
+ 'dependencies': [ 'MSI zh-TW 1028 (Chinese - Taiwan)' ],
+ 'actions':
+ [{
+ 'action_name': 'MSI Final',
+ 'message': '',
+ 'inputs': [ '<(build_dir_arch)/adblockplusie-INTERIM-<(target_arch).msi' ],
+ 'outputs': [ '<(build_dir_arch)/adblockplusie-FINAL-<(target_arch).msi' ],
+ 'action':
+ [
+ 'copy',
+ '<(build_dir_arch)/adblockplusie-INTERIM-<(target_arch).msi',
+ '<(build_dir_arch)/adblockplusie-FINAL-<(target_arch).msi',
+ ]
+ }]
+ },
+
+ ##################################
+ # END of MSI section
+ ##################################
+
+ #############
+ # Custom Action DLL for the installer
+ #############
+ {
+ 'target_name': 'installer-ca',
+ 'type': 'shared_library',
+ 'dependencies': [ 'installer-library' ],
+ 'sources':
+ [
+ 'src/custom-action/abp_ca.cpp',
+ 'src/custom-action/abp_ca.def',
+ 'src/custom-action/abp_ca.rc',
+ 'src/custom-action/close_application.cpp',
+ 'src/custom-action/close_ie.wxi',
+ ],
+ 'include_dirs':
+ [
+ 'src/installer-lib',
+ ],
+ 'link_settings':
+ {
+ 'libraries': [ 'user32.lib', 'Shell32.lib', 'advapi32.lib', 'msi.lib', 'Version.lib' ]
+ },
+ 'msvs_settings':
+ {
+ 'VCLinkerTool': {}
+ }
+ },
+
+ #############
+ # Windows Installer library
+ #############
+ {
+ 'target_name': 'installer-library',
+ 'type': 'static_library',
+ 'sources':
+ [
+ 'src/installer-lib/custom-i18n.h',
+ 'src/installer-lib/custom-i18n.wxi',
+ 'src/installer-lib/database.cpp',
+ 'src/installer-lib/database.h',
+ 'src/installer-lib/DLL.cpp',
+ 'src/installer-lib/DLL.h',
+ 'src/installer-lib/handle.h',
+ 'src/installer-lib/installer-lib.h',
+ 'src/installer-lib/interaction.cpp',
+ 'src/installer-lib/interaction.h',
+ 'src/installer-lib/process.cpp',
+ 'src/installer-lib/process.h',
+ 'src/installer-lib/property.cpp',
+ 'src/installer-lib/property.h',
+ 'src/installer-lib/record.cpp',
+ 'src/installer-lib/record.h',
+ 'src/installer-lib/session.cpp',
+ 'src/installer-lib/session.h',
+ ],
+ 'include_dirs':
+ [
+ 'src/installer-lib',
+ ],
+ 'direct_dependent_settings':
+ {
+ 'include_dirs':
+ [
+ 'src/installer-lib',
+ ],
+ },
+ 'link_settings':
+ {
+ 'libraries': [ 'user32.lib', 'Shell32.lib', 'advapi32.lib', 'msi.lib', 'Version.lib' ]
+ },
+ 'msvs_settings':
+ {
+ 'VCLinkerTool': {}
+ }
+ },
+
+ #############
+ # Custom actions for library test MSI
+ #############
+ {
+ 'target_name': 'installer-library-test-customactions',
+ 'type': 'shared_library',
+ 'dependencies':
+ [
+ 'installer-library',
+ ],
+ 'sources':
+ [
+ 'src/installer-lib/test/test-installer-lib-ca.cpp',
+ 'src/installer-lib/test/test-installer-lib-ca.def',
+ 'src/installer-lib/test/test-installer-lib-ca.rc',
+ 'src/installer-lib/test/test-installer-lib-sandbox.cpp',
+ 'src/installer-lib/test/custom-action-fail.cpp',
+ 'src/custom-action/close_application.cpp',
+ ],
+ },
+
+ #############
+ # WiX compile for library test MSI
+ #############
+ {
+ 'target_name': 'installer-library-test-wix',
+ 'type': 'none',
+ 'sources':
+ [
+ 'src/installer-lib/test/test-installer-lib.wxs',
+ 'src/installer-lib/custom-i18n.wxi',
+ ],
+ 'actions':
+ [ {
+ 'action_name': 'WiX compile',
+ 'message': 'Compiling WiX source',
+ 'inputs':
+ [
+ 'src/installer-lib/test/test-installer-lib.wxs'
+ ],
+ 'outputs':
+ [
+ '<(build_dir_arch)/test-installer-lib.wixobj'
+ ],
+ 'action':
+ [ 'candle -nologo -arch <(candle_arch) -dNoDefault ', '-out', '<@(_outputs)', '<@(_inputs)' ]
+ } ]
+ },
+
+ #############
+ # WiX link for library test MSI
+ #############
+ {
+ 'target_name': 'installer-library-test-msi',
+ 'type': 'none',
+ 'dependencies':
+ [
+ 'installer-library-test-customactions',
+ 'installer-library-test-wix',
+ ],
+ 'sources':
+ [
+ '<(build_dir_arch)/test-installer-lib.wixobj',
+ ],
+ 'actions':
+ [ {
+ 'action_name': 'WiX link',
+ 'message': 'Linking WiX objects',
+ 'linked_inputs':
+ [
+ '<(build_dir_arch)/test-installer-lib.wixobj',
+ ],
+ 'localization_input':
+ [
+ 'src/custom-action/close_ie_default.wxl', # Keep the .WXL file out of 'sources', since otherwise the custom rule will kick in
+ ],
+ 'inputs':
+ [
+ '<@(_linked_inputs)',
+
+ # Keep the .WXL file out of here, since otherwise the custom rule will kick in
+ # This isn't the best solution, since it means manual recompilation it this file changes,
+ # but it's easier to do this than to deal with how to change the default rule for '.wxl' that all the MSI targets use.
+ #'<@(_localization_input)',
+
+ 'src/custom-action/close_ie.wxi',
+ '<(build_dir_arch)/Debug/installer-library-test-customactions.dll'
+ ],
+ 'outputs':
+ [
+ '<(build_dir_arch)/test-installer-lib.msi'
+ ],
+ 'action':
+ # ICE71: The Media table has no entries
+ # Suppress ICE71 because the test MSI does not install any files.
+ [
+ 'light -notidy -nologo -ext WixUIExtension -sice:ICE71',
+ '<@(_linked_inputs)',
+ '-out', '<(build_dir_arch)/test-installer-lib.msi',
+ '-loc', '<@(_localization_input)'
+ ]
+ } ]
+ },
+
+ #############
+ # Custom Action unit tests
+ #############
+ {
+ 'target_name': 'installer-ca-tests',
+ 'type': 'executable',
+ 'dependencies':
+ [
+ 'installer-library',
+ 'installer-library-test-msi', # Some unit tests open the test MSI database
+ 'googletest.gyp:googletest_main',
+ ],
+ 'sources':
+ [
+ 'src/installer-lib/test/database_test.cpp',
+ 'src/installer-lib/test/exception_test.cpp',
+ 'src/installer-lib/test/process_test.cpp',
+ 'src/installer-lib/test/property_test.cpp',
+ 'src/installer-lib/test/record_test.cpp',
+ ],
+ 'link_settings':
+ {
+ 'libraries': [],
+ },
+ 'msvs_settings':
+ {
+ 'VCLinkerTool':
+ {
+ 'SubSystem': '1', # Console
+ },
+ },
+ },
+
+ ]
+}
+
+
+
Index: installer/msibuild.cmd
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/msibuild.cmd
@@ -0,0 +1,89 @@
+@echo off
+setlocal
+set FLAG=%1
+shift
+set LOCALE_ID=%1
+shift
+set LOCALE_NAME=%1
+shift
+set LOCALE_FILE=%1
+shift
+set MSI_LOCALE=%1
+shift
+set MST=%1
+shift
+set MSI_BASE=%1
+shift
+set MSI_INTERIM=%1
+shift
+set WIXOBJ=%1
+shift
+set WIXOBJ=%WIXOBJ% %1
+shift
+set WIXOBJ=%WIXOBJ% %1
+shift
+set WIXOBJ=%WIXOBJ% %1
+if "%WIXOBJ%"=="" goto Help
+if "%FLAG%"=="initial" (
+ set CULTURES=
+ goto Light
+)
+if "%FLAG%"=="additional" (
+ set CULTURES=-cultures:%LOCALE_NAME%
+ goto Light
+)
+echo First argument must be either 'initial' or 'additional'
+exit /b 1
+goto End
+:Light
+echo on
+light -notidy -nologo -ext WixUIExtension -sval %CULTURES% -loc %LOCALE_FILE% -out %MSI_LOCALE% %WIXOBJ%
+if errorlevel 1 GOTO :Error
+@echo off
+if "%FLAG%"=="additional" goto Additional
+:Initial
+echo on
+copy %MSI_LOCALE% %MSI_INTERIM%
+copy %MSI_LOCALE% %MSI_BASE%
+@echo off
+goto End
+:Additional
+echo on
+msitran -g %MSI_BASE% %MSI_LOCALE% %MST%
+if errorlevel 1 GOTO :Error
+cscript ..\..\emb.vbs %LOCALE_ID% %MSI_INTERIM% %MST%
+if errorlevel 1 GOTO :Error
+@echo off
+goto End
+
+:Help
+echo msibuild - A single-language step to create a multi-language MSI
+echo Must be run from an architecture-specific build directory, such as installer/build/ia32
+echo.
+echo usage: msibuild ^ ^ ^ ^ ^ ^ ^ ^
+echo flag - either 'initial' or 'additional'
+echo locale_id - a Microsoft LCID (e.g. 1033)
+echo locale_file - a WiX localization file (e.g. en-us.wxl)
+echo msi_locale - an MSI file as compiled with the given locale_file
+echo mst - an MST file generated by comparing msi_locale against msi_base
+echo msi_base - an MSI file against which to generate a transform
+echo msi_interim - an MSI file into which to embed the MST
+echo wix_objects - a list of WiX object files from which to build the MSI
+echo.
+echo Initial build
+echo - Create ^
+echo - Copy it to ^
+echo - Copy it to ^
+echo - Ignores arguments ^ (only needed for embedding) and ^ (since no transform is needed),
+echo but they must be present on the command line nonetheless
+echo.
+echo Additional build
+echo - Create ^
+echo - Generate ^ by comparing ^ against ^
+echo - Embed ^ into ^ with storage identifier ^
+echo.
+
+:Error
+exit /b 1
+
+:End
Index: installer/src/custom-action/abp_ca.def
===================================================================
--- a/installer/src/custom-action/abp_ca.def
+++ b/installer/src/custom-action/abp_ca.def
@@ -6,4 +6,4 @@
EXPORTS
; close_application.cpp
- abp_close_applications
+ abp_close_ie
Index: installer/src/custom-action/close_application.cpp
===================================================================
--- a/installer/src/custom-action/close_application.cpp
+++ b/installer/src/custom-action/close_application.cpp
@@ -2,44 +2,458 @@
* \file close_application.cpp
*/
+#include
+
#include "session.h"
#include "property.h"
#include "database.h"
+#include "process.h"
+#include "interaction.h"
+#include "custom-i18n.h"
+const wchar_t * ie_names[] = { L"IExplore.exe" } ;
+const wchar_t * engine_names[] = { L"AdblockPlusEngine.exe" } ;
+const wchar_t * abp_module_names[] = { L"AdblockPlus32.dll", L"AdblockPlus64.dll" } ;
+
+//-------------------------------------------------------
+//-------------------------------------------------------
+class IE_Closer
+{
+ Process_Snapshot snapshot ;
+
+ Process_Closer ie_closer ;
+
+ Process_Closer engine_closer ;
+
+public:
+ IE_Closer()
+ : snapshot(), ie_closer( snapshot, ie_names, abp_module_names), engine_closer( snapshot, engine_names )
+ {}
+
+ void refresh()
+ {
+ snapshot.refresh() ;
+ ie_closer.refresh() ;
+ engine_closer.refresh() ;
+ }
+
+ bool is_running()
+ {
+ return ie_closer.is_running() || engine_closer.is_running() ;
+ }
+
+ bool shut_down()
+ {
+ if ( ie_closer.is_running() && ! ie_closer.shut_down() )
+ {
+ // Assert IE is still running
+ // This is after we've tried to shut it down, so we fail
+ return false ;
+ }
+ if ( engine_closer.is_running() && ! engine_closer.shut_down() )
+ {
+ // Assert the engine is still running
+ // This is after IE has shut down itself and after we've tried to shut down the engine. Whatever.
+ return false ;
+ }
+ return true ;
+ }
+} ;
+
+
+//-------------------------------------------------------
+// abp_close_ie
+//-------------------------------------------------------
/**
* Exposed DLL entry point for custom action.
* The function signature matches the calling convention used by Windows Installer.
*
+ * This function supports four policy stances with respect to a running IE process.
+ *
+ * - Allow reboot.
+ * IE is running, so what? I'm willing to reboot after installation.
+ * - Avoid reboot passively.
+ * I don't want to affect any other running processes, but I don't want to reboot. I'll abort the installation.
+ * - Avoid reboot actively.
+ * I want to shut down IE in order to avoid a reboot.
+ * I'll do it manually when the time is right.
+ * - Avoid reboot automatically.
+ * I want to shut down IE automatically in order to avoid a reboot.
+ *
+ * In a silent installation, the default stance is "allow reboot", which is to say, to act like most installers.
+ * In an interactive installation, the stance is gathered from the user through dialog boxes.
+ * If the MSI property AVOIDREBOOT is set to one of the values NO, PASSIVE, ACTIVE, or AUTOMATIC, the policy is set accordingly.
+ * In a silent installation, this is the only way getting a stance other than the default.
+ * In an interactive installation, AVOIDREBOOT skips the dialogs.
+ *
* \param[in] session_handle
* Windows installer session handle
+ *
+ * \return
+ * An integer interpreted as a custom action return value.
+ *
+ * \post
+ * + The return value is one of the following:
+ * - ERROR_INSTALL_USEREXIT if the user cancelled installation.
+ * - ERROR_INSTALL_FAILURE if something unexpected happened, usually if the top level try-block caught an exception.
+ * - ERROR_SUCCESS otherwise.
+ * + The function performed at least one check that Internet Explorer was running.
+ * + If ERROR_SUCCESS, the MSI property RUNNINGBROWSER is set and has one of the following values:
+ * - 1 if Internet Explorer was running upon the last check.
+ * - 0 otherwise.
+ * \post
+ * Note that this function cannot provide any assurance that Internet Explorer stays either stays running or stays not running.
+ *
+ * \sa
+ * - MSDN [Custom Action Return Values](http://msdn.microsoft.com/en-us/library/aa368072%28v=vs.85%29.aspx)
*/
extern "C" UINT __stdcall
-abp_close_applications( MSIHANDLE session_handle )
+abp_close_ie( MSIHANDLE session_handle )
{
- // Always supply an externally-exposed function with a catch-all block
+ // Utility typedef to shorten the class name.
+ typedef Installer_Message_Box IMB ;
+
+ /*
+ * Immediate_Session cannot throw, so it can go outside the try-block.
+ * It's needed in the catch-all block to write an error message to the log.
+ */
+ Immediate_Session session( session_handle, "abp_close_ie" ) ;
+
+ // The body of an entry point function must have a catch-all.
try {
- Immediate_Session session( session_handle, L"abp_close_applications" ) ;
- session.log( L"Have session object" ) ;
+ // MSI property BROWSERRUNNING is one of the return values of this function.
+ Property browser_running( session, L"BROWSERRUNNING" ) ;
+ Property browser_closed( session, L"BROWSERCLOSED" ) ;
+
+ // Instantiation of Process_Closer takes a snapshot.
+ IE_Closer iec ;
+
+ /*
+ * We take the short path through this function if neither IE nor engine is not running at the outset.
+ */
+ if ( ! iec.is_running() )
+ {
+ browser_running = L"0" ; // The browser is not running.
+ browser_closed = L"0" ; // We didn't close the browser (and we couldn't have).
+ session.log( "IE not running. No issue with reboot policy." ) ;
+ return ERROR_SUCCESS ;
+ }
+
+ /*
+ * As a (potentially) user-driven function, a state machine manages control flow.
+ * The states are organized around the policy stances.
+ */
+ enum Policy_State {
+ // Non-terminal states
+ not_known, // We don't know the user's stance at all
+ part_known, // The user has indicated either ACTIVE or AUTOMATIC
+ active, // Actively avoid reboot
+ automatic, // Automatically avoid reboot
+ // Terminal states
+ success,
+ abort,
+ // Aliases for what would ordinarily be non-terminal states.
+ // They're terminal because of implementation details.
+ allow = success, // Allow reboot.
+ passive = abort, // Passively avoid reboot, that is, don't try to close IE.
+ };
+ Policy_State state ;
+
+ /*
+ * Use the AVOIDREBOOT property, if present, to set an initial state.
+ */
+ std::wstring avoid_reboot = Property( session, L"AVOIDREBOOT" ) ;
+ std::transform( avoid_reboot.begin(), avoid_reboot.end(), avoid_reboot.begin(), ::towupper ) ;
+ if ( avoid_reboot == L"" )
+ {
+ state = not_known ;
+ }
+ else if ( avoid_reboot == L"NO" )
+ {
+ state = allow ;
+ session.log( "Reboot allowed on command line." ) ;
+ }
+ else if ( avoid_reboot == L"PASSIVE" )
+ {
+ state = passive ;
+ session.log( "Reboot avoided on command line." ) ;
+ }
+ else if ( avoid_reboot == L"ACTIVE" )
+ {
+ state = active ;
+ }
+ else if ( avoid_reboot == L"AUTOMATIC" )
+ {
+ state = automatic ;
+ }
+ else
+ {
+ // It's an error to specify an unrecognized value for AVOIDREBOOT.
+ throw std::runtime_error( "unrecognized value for AVOIDREBOOT" ) ;
+ }
+
+ /*
+ * When running as an update (see Updater.cpp), this installer receives the command line option "/qb",
+ * which sets the UI level to "Basic UI".
+ * When running as an initial install, we cannot expect what command line options this installer receives.
+ */
+ /*
+ * The UILevel property indicates whether we have the ability to put dialog boxes up.
+ * Levels 2 (silent) and 3 (basic) do not have this ability.
+ * Levels 4 (reduced) and 5 (full) do.
+ *
+ * MSDN [UILevel property](http://msdn.microsoft.com/en-us/library/windows/desktop/aa372096%28v=vs.85%29.aspx)
+ */
+ std::wstring uilevel = Property( session, L"UILevel" ) ;
+ bool interactive ;
+ if ( uilevel == L"5" || uilevel == L"4" )
+ {
+ interactive = true ;
+ // Assert state is one of { not_known, allow, passive, active, automatic }
+ }
+ else if ( uilevel == L"3" || uilevel == L"2" )
+ {
+ // Assert installer is running without user interaction.
+ interactive = false ;
+ if ( state == not_known )
+ {
+ // Assert AVOIDREBOOT was not specified
+ /*
+ * This is where we specify default behavior for non-interactive operation.
+ * The choice of "allow" makes it act like other installers, which is to make no effort to avoid a reboot after installation.
+ */
+ state = allow ;
+ session.log( "Reboot allowed by default in non-interactive session." ) ;
+ }
+ else if ( state == active )
+ {
+ throw std::runtime_error( "AVOIDREBOOT=ACTIVE in non-interative session is not consistent" ) ;
+ }
+ // Assert state is one of { allow, passive, automatic }
+ }
+ else
+ {
+ throw std::runtime_error( "unrecognized value for UILevel" ) ;
+ }
+
+ /*
+ * Now that preliminaries are over, we set up the accessors for UI text.
+ * We only use the object 'message_text' for interactive sessions, but it's cheap to set up and a hassle to conditionalize.
+ *
+ * The key "close_ie" is the component name within the file "close_ie.wxi" that defines rows in the localization table.
+ * The identifiers for the message_text.text() function are defined within that file.
+ */
Installation_Database db( session ) ;
- session.log( L"Have database object" ) ;
+ custom_message_text message_text( db, L"close_ie" ) ;
- session.log( L"Still with new Property operator+ implementations!" ) ;
- session.log( L"VersionMsi = " + Property( session, L"VersionMsi" ) ) ;
+ /*
+ * State machine: Loop through non-terminal states.
+ *
+ * Loop invariant: IE was running at last check, that is, iec.is_running() would return true.
+ */
+ while ( state <= automatic ) // "automatic" is the non-terminal state with the highest value
+ {
+ switch ( state )
+ {
+ case not_known:
+ /*
+ * Precondition: interactive session
+ *
+ * Ask the user "Would you like to close IE and avoid reboot?"
+ * Yes -> Close IE somehow. Goto part_known.
+ * No -> Install with reboot. Goto allow.
+ * Cancel -> terminate installation. Goto abort.
+ */
+ {
+ int x = session.write_message( IMB( message_text.text( L"dialog_unknown" ), IMB::warning_box, IMB::yes_no_cancel, IMB::default_button_three ) ) ;
+ switch ( x )
+ {
+ case IDYES:
+ state = part_known ;
+ break ;
+ case IDNO:
+ state = allow ;
+ session.log( "User chose to allow reboot" ) ;
+ break ;
+ case IDCANCEL:
+ state = abort ;
+ session.log( "User cancelled installation" ) ;
+ break ;
+ default:
+ throw unexpected_return_value_from_message_box() ;
+ }
+ }
+ break ;
- Property tv( session, L"TESTVARIABLE" ) ;
- session.log( L"TESTVARIABLE = " + tv ) ;
- session.log( L"Setting TESTVARIABLE to 'testvalue'" ) ;
- tv = L"testvalue" ;
- session.log( L"TESTVARIABLE = " + tv ) ;
+ case part_known:
+ /*
+ * Precondition: interactive session
+ *
+ * Ask the user "Would you like the installer to close IE for you?"
+ * Yes -> Goto automatic
+ * No -> Goto active
+ * Cancel -> Goto not_known
+ */
+ {
+ int x = session.write_message( IMB( message_text.text( L"dialog_part_known" ), IMB::warning_box, IMB::yes_no_cancel, IMB::default_button_three ) ) ;
+ switch ( x )
+ {
+ case IDYES:
+ state = automatic ;
+ break ;
+ case IDNO:
+ state = active ;
+ break ;
+ case IDCANCEL:
+ state = not_known ;
+ break ;
+ default:
+ throw unexpected_return_value_from_message_box() ;
+ }
+ }
+ break ;
+
+ case active:
+ /*
+ * Precondition: interactive session
+ *
+ * IE is no longer running -> Goto success
+ * IE is still running ->
+ * Ask the user to close IE manually
+ * OK -> re-enter this state
+ * Cancel -> Goto not_known
+ */
+ {
+ int x = session.write_message( IMB( message_text.text( L"dialog_active_retry" ), IMB::warning_box, IMB::ok_cancel, IMB::default_button_one ) ) ;
+ switch ( x )
+ {
+ case IDOK:
+ /*
+ * Refresh our knowledge of whether IE is running.
+ * If it is, we display the dialog again. The state doesn't change, so we just iterate again.
+ * If it's not, then the user has closed IE and we're done.
+ */
+ iec.refresh() ;
+ if ( ! iec.is_running() )
+ {
+ state = success ;
+ session.log( "User shut down IE manually." ) ;
+ }
+ break ;
+ case IDCANCEL:
+ state = not_known ;
+ break ;
+ default:
+ throw unexpected_return_value_from_message_box() ;
+ }
+ }
+ break ;
+
+ case automatic:
+ /*
+ * Close all known IE instances.
+ * Unlike other cases, this state starts with an action and not a user query.
+ * We first shut down IE, or at least attempt to.
+ *
+ * Succeeded -> Goto success
+ * Failed && interactive ->
+ * Ask user if they would like to try again
+ * Retry -> re-enter this state
+ * Cancel -> Goto not_known
+ * Failed && not interactive -> Goto abort
+ */
+ {
+ bool IE_was_closed = iec.shut_down() ;
+ if ( iec.is_running() )
+ {
+ session.log( "Attempt to shut down IE automatically failed." ) ;
+ if ( interactive )
+ {
+ // Assert Interactive session and IE did not shut down.
+ int x = session.write_message( IMB( message_text.text( L"dialog_automatic_retry" ), IMB::warning_box, IMB::retry_cancel, IMB::default_button_one ) ) ;
+ switch ( x )
+ {
+ case IDRETRY:
+ // Don't change the state. Iterate again.
+ break ;
+ case IDCANCEL:
+ state = not_known ;
+ break ;
+ default:
+ throw unexpected_return_value_from_message_box() ;
+ }
+ }
+ else
+ {
+ // Assert Non-interactive session and IE did not shut down.
+ state = abort ;
+ session.log( "Failed to shut down IE automatically." ) ;
+ }
+ }
+ else
+ {
+ // Assert IE is not running, so shut_down() succeeded.
+ state = success ;
+ session.log( "Automatically shut down IE." ) ;
+ }
+ }
+ break;
+ }
+ }
+ /*
+ * State machine: Actions for terminal states.
+ */
+ switch ( state )
+ {
+ case success:
+ if ( iec.is_running() )
+ {
+ browser_running = L"1" ;
+ browser_closed = L"0" ;
+ }
+ else
+ {
+ browser_running = L"0" ;
+ browser_closed = L"1" ;
+ }
+ return ERROR_SUCCESS ;
+ break;
+ case abort:
+ return ERROR_INSTALL_USEREXIT ;
+ break;
+ }
+ }
+ catch( std::exception & e )
+ {
+ session.log_noexcept( "terminated by exception: " + std::string( e.what() ) ) ;
+ return ERROR_INSTALL_FAILURE ;
}
catch( ... )
{
+ session.log_noexcept( "terminated by unknown exception" ) ;
return ERROR_INSTALL_FAILURE ;
}
-
- /*
- * While we're working on infrastructure (and not the CA itself), fail the action.
- */
+ // Should be unreachable.
return ERROR_INSTALL_FAILURE ;
}
+
+/*
+ * EnumWindows system call: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633497%28v=vs.85%29.aspx
+ */
+/**
+ *
+ * Callback function for EnumWindows.
+ */
+BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
+{
+ return TRUE ;
+}
+
+/**
+ * Windows_List
+ */
+class Window_List {
+public:
+ void enumerate_top_level();
+};
Index: installer/src/custom-action/close_ie.wxi
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/custom-action/close_ie.wxi
@@ -0,0 +1,30 @@
+
+
+
+
+
+ close_ie
+ dialog_unknown
+ !(loc.close_ie__dialog_unknown)
+
+
+ close_ie
+ dialog_part_known
+ !(loc.close_ie__dialog_part_known)
+
+
+ close_ie
+ dialog_active_retry
+ !(loc.close_ie__dialog_active_retry)
+
+
+ close_ie
+ dialog_automatic_retry
+ !(loc.close_ie__dialog_automatic_retry)
+
+
+
Index: installer/src/custom-action/close_ie_default.wxl
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/custom-action/close_ie_default.wxl
@@ -0,0 +1,12 @@
+
+
+
+ IE is still running.
Would you like to shut down IE in order to avoid having to reboot?
+ Would you like the installer to close IE for you?
+ IE is still running. Please close it and then click OK.
+ The installer failed to close IE.
Would you like try again?
+
Index: installer/src/custom-action/test/test-close-application.wxs
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/custom-action/test/test-close-application.wxs
@@ -0,0 +1,827 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = 600 ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: installer/src/documentation/build_process.dox
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/documentation/build_process.dox
@@ -0,0 +1,110 @@
+/*!
+
+\page build_process Build Process
+
+## Tools
+
+\par WiX
+
+- WiX
+ [WiX Toolset] (http://wixtoolset.org/)
+ Home page.
+- Wix
+ [WiX Toolset Manual Table of Contents] (http://wixtoolset.org/documentation/manual/v3/)
+ Where you usually need to go first.
+- WiX
+ [Compiler] (http://wixtoolset.org/documentation/manual/v3/overview/candle.html)
+ A scandalously defective page about the compiler, called `candle`.
+ You'll need to run `candle.exe -h` to see anything about its command line options.
+- WiX
+ [Linker (light)] (http://wixtoolset.org/documentation/manual/v3/overview/light.html)
+
+
+\par Windows Installer Development Tools
+
+The Windows Installer Development Tools are a set of tools for manipulating MSI and other Windows Installer files.
+Microsoft has delivered these tools in different ways in past.
+Now they're included in the Microsoft Windows SDK, which the plugin already uses for ATL.
+
+We require the tool `Msitran.exe` for the build.
+The tool `Msiinfo.exe` displays the Summary Information of an installer file,
+ which is useful for auditing the build process.
+It can also be used to set properties in the Summary Information,
+ but we're not using that feature.
+
+- MSDN
+ [Windows Installer Development Tools] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa372834%28v=vs.85%29.aspx)
+- MSDN
+ [Windows SDK Components for Windows Installer Developers] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa370834%28v=vs.85%29.aspx)
+
+
+\par Windows Installer Examples
+
+The MSDN pages for the Windows Installer include a section of scripting examples.
+We're using the script `emb.vbs` from this section to embed transforms.
+
+- MSDN
+ [Windows Installer Examples] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa372837%28v=vs.85%29.aspx)
+
+
+## Building the Base Installer
+
+_To write_
+
+## Building the Localized Installer
+
+Understanding \ref localization is necessary for this section to make sense.
+
+
+
+- installsite.org
+ [Multi-Language MSI Packages without Setup.exe Launcher] (http://www.installsite.org/pages/en/msi/articles/embeddedlang/)
+ The original page that documents the automatic application of embedded language transforms.
+ First written by Andreas Kerl at Microsoft Germany and then translated into English.
+- GeekTieGuy
+ [Create a multi-lingual / multi-language MSI using WiX and custom build scripts] (http://www.geektieguy.com/2010/03/13/create-a-multi-lingual-multi-language-msi-using-wix-and-custom-build-scripts/)
+ Someone else's build process using the same information.
+
+\par Generating a Language Transform
+
+The only way of generating a language transform is to use the moral equivalent of a `diff` tool.
+This requires two complete MSI files.
+One of them is the default-language MSI; the other is a single-language MSI.
+For reliability, we generate a transform based on the full MSI, including all the payloads,
+ which means the single-language MSI needs all the payloads as well.
+This is a lot of I/O for the build, but there's no way of cutting corners here,
+ at least not without writing a fully custom tool to generate transforms directly.
+
+The `Msitran.exe` tool is used to generate the transform.
+There's an equivalent WiX tool, but it has a defect that makes it unsuitable for us;
+ it omits some elements in the generated transform.
+
+- MSDN
+ [Msitran.exe] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa370495%28v=vs.85%29.aspx)
+
+
+\par Embedding Language Transforms
+
+Once we have an MST file, we need to embed it into an MSI file.
+The tool we're using is a Visual Basic script `emb.vbs`, provided by Microsoft.
+Its arguments are an MSI file, an MST file, and the name of the substorage (directory).
+It's a short script that utilizes the Automation interface of Windows Installer.
+
+The tool `Msidb.exe` might have been used for this purpose,
+ but it lacks a command line option to specify the name of the substorage.
+
+Another script `WiSubStg.vbs` was used to embed transforms in the original installsite.org page.
+It accomplishes the same result as `emb.vbs`, though it's longer and does more.
+The name of this other script, though, makes a useful search term.
+
+- MSDN
+ [Embedding Customization Transforms as Substorage] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa368351%28v=vs.85%29.aspx)
+ Source code listing for `emb.vbs`.
+- MSDN
+ [Msidb.exe] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa370083%28v=vs.85%29.aspx)
+- MSDN Windows Installer Scripting Examples
+ [Manage Substorages] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa369793%28v=vs.85%29.aspx)
+ Documentation on the script `WiSubStg.vbs`.
+
+
+*/
\ No newline at end of file
Index: installer/src/documentation/localization.dox
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/documentation/localization.dox
@@ -0,0 +1,219 @@
+/*!
+
+\page localization Localization
+
+The Windows Installer originally dates from 1999, and it hasn't kept up with the times.
+Most problematic is that as of this writing [2013 Nov] it doesn't have official support for Unicode.
+Support is largely undocumented, and in any case is only partial.
+This causes all sorts of problems with modern tools, many of which default to UTF-8.
+WiX, as XML, does not exactly default to UTF-8, but it's close to it by convention.
+Some languages are Unicode-only, notably those based on Indic scripts.
+
+Overall, these limitation generate two kinds of issues.
+The first class is deficits in the shipped product.
+In short, some strings in certain languages simply cannot be localized.
+The second class is a burden on compliance testing before release.
+Unicode support is not official in Windows Installer; indeed it's only partial support at that.
+But also the WiX toolset does not provide a complete set of assurance tools,
+ even though all the necessary compiler mechanisms seem to be present.
+As a result, manual verification of localization must occur for each language.
+
+\par MSI format
+
+The format of Windows Installer affects these issues.
+While it's a proprietary format, it's not totally opaque.
+Overall, it's uses the [COM Structured Storage][structured_storage] format,
+ which is just an archive file with an internal file system.
+In their lingo, a "storage" is a directory and "stream" is a file.
+Two important aspects of localization use mechanisms whose documentation uses these terms.
+The "Summary Information Stream" is a file that provides metadata for the installer database.
+Embedded language transforms sit in the final MSI as storages.
+Some commentary on this topic is available on Rob Mensching's blog;
+ he's the principal author of WiX and a former member of the Windows Installer team.
+- Mensching
+ [Inside the MSI file format] (http://robmensching.com/blog/posts/2003/11/25/inside-the-msi-file-format)
+- Mensching
+ [Inside the MSI file format, again] (http://robmensching.com/blog/posts/2004/2/10/inside-the-msi-file-format-again)
+
+[structured_storage]: http://msdn.microsoft.com/en-us/library/aa380369%28VS.85%29.aspx
+
+The Summary Information Stream is an important structure for two reasons.
+The first is that it needs to be localized.
+It contains the strings that appear in the Control Panel tool that lists all installed programs.
+This includes basic things like the name of the program.
+The second is that one of its fields lists the languages supported in a multiple-language installer.
+- MSDN
+ [Summary Property Descriptions] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa372049%28v=vs.85%29.aspx)
+
+It's worth noting that these summary properties were not originally designed for installation.
+Rather, they are derived from the summary information for documents defined by the COM structured storage interface.
+The names originally used for these fields were carried over to the SIS for installers,
+ even when they didn't fit at all,
+One example is use of the page count field to represent the minimum required installer version.
+Likewise, certain fields were dropped entirely, such as "total editing time".
+- MSDN
+ [Summary Information Property Set] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa380376%28v=vs.85%29.aspx)
+ This page is about COM Structured Storage, not Windows Installer.
+
+The tool OpenMCDF, available on sourceforge, is a .NET component to manipulate structured storage.
+It contains a simple structured storage browser that can show if embedded transforms are present.
+- SourceForge
+ [OpenMCDF] (http://sourceforge.net/projects/openmcdf/)
+
+
+\par Installation Strings
+
+A simple MSI contains two internal files that contain localizable strings.
+- An installation database itself.
+ This is a set of tables, some of which contain properties and UI strings.
+- The Summary Information Stream.
+ This data appears principally in the Add/Remove Programs Menu within the Control Panel.
+We're also using embedded directories (storages) to store language-specific transforms.
+These transforms, however, are generated from simple MSI's that contain only the two files above.
+
+These two files are localized differently and have different levels of support for Unicode.
+In short, the installer database mostly supports Unicode and the SIS does not.
+Unicode is not officially supported anywhere, but it seems to work in practice for installer databases.
+The SIS is another story.
+Trying to use Unicode in the SIS leads to Windows thinking the MSI is malformed and refusing to install it.
+The SIS can be localized into many languages, but not all; see the section on code pages below.
+
+
+\par Language Identifiers
+
+Microsoft has its own system of identifying languages.
+(Could it have been otherwise?)
+A language is identified with an LCID, which variously stands for language code identifier or locale identifier.
+The inconsistency is internal to Microsoft, because it itself doesn't have a completely consistent usage of LCID.
+The earlier LCID is the one accepted by Windows system calls;
+ the later one is used for the .NET library.
+They are mostly the same, but they're not identical.
+
+Windows installer does not itself use any of the textual identifiers that are available,
+ such as ISO 693 or its IETF usage.
+WiX has some support for textual identifiers, but doesn't document how LCID's are generated from them.
+As a result, WiX should be considered unreliable for this.
+LCID reference tables are usually specified as hexadecimal constant,
+ since the LCID itself is a bit field.
+Infuriatingly, WiX doesn't support hexadecimal integer constants,
+ requiring a conversion of the LCID to decimal.
+
+The notion of a character set is given by a code page,
+ which is simply an assignment of character strings to glyph strings.
+It's not as simple as a character-to-glyph map,
+ since a double-byte character set (DBCS) uses an internal escape code
+ and mixes single- and double-byte characters.
+Regardless of these details, the important distinction is between the so-called "ANSI code pages" and Unicode.
+
+- MSDN
+ [Language Identifiers] (http://msdn.microsoft.com/en-us/library/dd318691%28v=vs.85%29.aspx)
+ The format of LCID as the operating system recognizes them.
+- MSDN
+ [Language Identifier Constants and Strings] (http://msdn.microsoft.com/en-us/library/dd318693%28v=vs.85%29.aspx).
+ Note: This is for the .NET version, but contains enough information to derive the Window system call version.
+
+
+\par Code Pages
+
+The SIS is the only place where code pages really matter, because the SIS doesn't support Unicode.
+
+Microsoft has its own system of character sets, called code pages.
+Code pages predate Unicode, and while Microsoft is shifting over to Unicode, the transition isn't complete.
+As long as the Windows Installer doesn't fully support Unicode, code pages will be relevant.
+Even afterwards, they'll remain relevant until the first working version is the oldest one requiring support.
+
+A code page specifies an encoding from byte strings to character strings.
+The details aren't particularly relevant to installation issues.
+What matters are the identifiers.
+With Unicode, you can settle on a single identifier, "UTF-8", that represents an encoding.
+Code page identifiers are 16-bit integers.
+The most common one is 1252 (Latin-1), used for English and most Western European languages.
+The code page identifier 65001 means UTF-8, but it's not universally supported.
+
+Even within the Microsoft environment, code page identifiers are not completely consistent.
+The Windows operating system accepts a basic set of code pages;
+ overall these are alphabetic writing systems.
+There are also OEM code pages and non-native code pages.
+Code pages are used by Windows for font selection (amongst other things).
+Either or both of the code page itself and the requisite display fonts might be absent on a machine.
+Furthermore, .NET introduced some others, such one for UTF-16LE,
+ but these are only available to managed applications.
+Luckily, there don't seem to be any multiply-assigned code page identifiers.
+All this matters because various tables of code page identifiers tend not to distinguish clearly between these categories.
+Even armed with a table, the developer or a linguist should be cautious about selecting a code page.
+
+The SIS uses, however, so-called "ANSI code pages", sometimes called "Windows code pages".
+This doesn't seem to be particularly well-defined term.
+It seems to mean "acceptable to a system call ending with A and not W",
+ but even that's not clear.
+What is clear is that even though 65001 for UTF-8 appears in some tables of ANSI code pages,
+ it isn't one insofar as Windows Installer is concerned.
+Caveat emptor, indeed.
+
+- MSDN
+ [Code Pages] (http://msdn.microsoft.com/en-us/library/windows/desktop/dd317752%28v=vs.85%29.aspx)
+ Commentary and historical information.
+- Wikipedia
+ [Code page] (http://en.wikipedia.org/wiki/Code_page)
+ Do not take as authoritative, but nevertheless useful for understanding some things you might see elsewhere.
+- MSDN
+ [Code Page Identifiers] (http://msdn.microsoft.com/en-us/library/dd317756%28VS.85%29.aspx)
+- Heath Stewart's blog
+ [MSI Databases and Code Pages] (http://blogs.msdn.com/b/heaths/archive/2005/10/05/msi-databases-and-code-pages.aspx)
+ Explains how older tools dealt with this issue; WiX is much easier.
+ Contains some useful background information.
+- Mailing list wix-users
+ [Build time selection of codepage...] (http://comments.gmane.org/gmane.comp.windows.devel.wix.user/44388)
+ One developer decided not to localize the SIS at all.
+- [Character Sets And Code Pages At The Push Of A Button] (http://www.i18nguy.com/unicode/codepages.html)
+ A wealth of information about the specific code page and other encodings.
+ Hopefully we never need to dig this far in.
+
+
+\par Multiple-language Installers
+
+The Windows Installer does not have good mechanisms for multiple languages.
+Each installation package is a single-language installer at the time of installation.
+In some situations it's feasible to simply ship out single-language installers,
+ such as internal deployment within a multinational company.
+In others, where multiple languages are required, this leads to bloat because of duplication of installation assets.
+
+The documented way to support this situation is to deliver two assets:
+ (1) a basic MSI and (2) a transform (MST) that changes the language of the MSI.
+There's syntax on the `msiexec` command line to apply a transform before installation.
+For consumer software, this is too much, so people started using an installation driver executable.
+This executable would typically present a menu for the locale,
+ or sometimes infer it from the environment.
+After selecting a locale, it would select and apply an appropriate transform.
+This is the documented way of doing things.
+
+There is, however, an undocumented way to automatically apply a transform.
+The Summary Information has a property called `Template` that contains a list of languages supported.
+These languages are specified by a language identifer (LCID), a decimal integer,
+ whose sublanguage may either be generic (zero) or specific.
+For example, 1033 is US English.
+The first (or only) language on the list specifies the language of the non-transformed installation database.
+Each subsequent identifier specifies an additional language.
+The MSI must contain an embedded transform for each additional language.
+(If it doens't, Windows Installer throws up an error message and aborts installation.)
+An embedded transform is an MST file in a substorage (directory) whose name is the decimal LCID.
+Windows Installer checks the language list before installation proper begins to determine the most appropriate language.
+If finds one that's not first on the list, it applies its embedded transform before installation proper begins.
+Otherwise it uses the installer database as-is, without any transformation.
+
+- installsite.org
+ [Multi-Language MSI Packages without Setup.exe Launcher] (http://www.installsite.org/pages/en/msi/articles/embeddedlang/)
+ The original page that documents the automatic application of embedded language transforms.
+ First written by Andreas Kerl at Microsoft Germany and then translated into English.
+- MSDN
+ [Template Summary property] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa372070%28v=vs.85%29.aspx)
+ Documents the format of the Template property in the SIS.
+ From this page: "Merge Modules are the only packages that may have multiple languages."
+ Contrary to this quotation, MSI files are also allowed to have multiple languages, although such support is undocumented.
+- MSDN
+ [Embedded Transforms] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa368347%28v=vs.85%29.aspx)
+ This page not only isn't very informative, it's also slightly wrong.
+ There's an implication that an embedded transform is stored as a file, which is only sort-of right;
+ the actual mechanism is as a substorage (directory).
+
+*/
\ No newline at end of file
Index: installer/src/documentation/mainpage.dox
===================================================================
--- a/installer/src/documentation/mainpage.dox
+++ b/installer/src/documentation/mainpage.dox
@@ -2,7 +2,44 @@
\mainpage Installer for ABP-IE Documentation
-\par Entry Points for Custom Action library
+The main source code for the installer uses WiX.
+WiX, however, is simply a front end to create Windows Installer assets.
+Most of the documentation needed to understand how the installer works is on MSDN, not on the WiX site.
+Unfortunately, the WiX documentation does a poor job of linking itself into MSDN.
+
+- MSDN
+ [Windows Installer] (http://msdn.microsoft.com/en-us/library/windows/desktop/cc185688%28v=vs.85%29.aspx)
+ Top page for the Windows Installer on MSDN.
+- WiX
+ [WiX Toolset] (http://wixtoolset.org/)
+ Home page.
+
+\par Internal Pages
+
+- \subpage localization.
+ Localization of Windows Installer files is idiosyncratic at best.
+ This page surveys all the relevant data structures required for localization of the installer.
+- \subpage source_code.
+ Notes on source code organization and structure.
+ Some of this is supplementary WiX documentation, where that's lacking.
+- \subpage build_process.
+ Largely because of localization, the build process is rather involved.
+ The page outlines the process and provides rationale for why it works this way.
+
+\par Custom Action library
+
+The custom action library is used to ensure that IE is closed during installation.
+This prevents a reboot as part of the installation process.
+
+The library uses a number of Windows Installer database functions.
+
+- MSDN
+ [Database Functions] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa368250%28v=vs.85%29.aspx)
+- MSDN
+ [Functions Not for Use in Custom Actions] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa368612%28v=vs.85%29.aspx)
+
+\par DLL Entry Points
+
- DllMain()
- abp_close_applications()
Index: installer/src/documentation/source_code.dox
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/documentation/source_code.dox
@@ -0,0 +1,108 @@
+/*!
+
+\page source_code Source Code
+
+## Localization Source ##
+
+See \subpage localization for all the relevant MSI data structures.
+
+\par WiX `.loc` localization files
+
+- WiX
+ [WixLocalization Element] (http://wixtoolset.org/documentation/manual/v3/xsd/wixloc/wixlocalization.html)
+
+\par WiX sources for SIS strings
+
+The WiX documentation isn't always very explicit about how it maps WiX elements and attributes to MSI values.
+Most of them are in the WiX elements `Product` and `Package`.
+The documentation for `Package` elements states that it is "properties about the package to be placed in the Summary Information Stream."
+That, however, is misleading, since it's not the only source of such properties.
+ nor does quite every attribute in the element appear in the SIS.
+
+- MSDN
+ [Summary Property Descriptions] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa372049%28v=vs.85%29.aspx)
+- WiX
+ [Product Element] (http://wixtoolset.org/documentation/manual/v3/xsd/wix/product.html)
+- WiX
+ [Package Element] (http://wixtoolset.org/documentation/manual/v3/xsd/wix/package.html)
+
+This section enumerates the properties in the SIS and shows where they originate in the source code.
+They are listed in order of their property index numbers, the same order used by the `msiinfo`.
+The properties in the SIS appear in the Control Panel tool _Programs and Features_.
+
+- **Codepage**.
+ In the attribute `Package/@SummaryCodepage`.
+ This is the code page used to display strings in the Summary Information.
+- **Title**.
+ The fixed string "Installation Database".
+- **Subject**.
+ In the attribute `Package/@Description`.
+ Appears as the "Name" field in the tabular listing in Programs and Features.
+- **Author**.
+ In the attribute `Package/@Manufacturer`.
+ If omitted, (apparently) defaults to `Product/@Manufacturer`.
+ Appears as the "Publisher" field in the tabular listing in Programs and Features.
+ Related to this, there's a property "Manufacturer" in the installer database.
+- **Keywords**.
+ In the attribute `Package/@Keywords`.
+ Appears at the bottom of Programs and Features when the package is selected. (?)
+- **Comments**.
+ In the attribute `Package/@Comments`.
+ Appears at the bottom of Programs and Features when the package is selected.
+- **Template**.
+ This single SIS property is the concatenation of two fields.
+ The first is the value of the `-arch` command line option of the compiler `candle`.
+ If that's not specified, it's in `Package/@Platform` or `Package/@Platforms`.
+ The second is in `Package/@Languages`.
+- **Revision**.
+ Used to store the package GUID
+ In attribute `Package/@Id`, but is automatically generated if absent,
+ which is the recommended usage.
+- **Last Printed**.
+ Used for administrative images.
+ Not a concern for us, but set by a tool.
+- **Create Time/Date**.
+ The linker sets this value.
+ So does the embedding tool, which is arguably a defect in the Automation interface.
+- **Last Saved Time/Date**.
+ The linker sets this value, as do the other tools that modify the MSI.
+- **Page Count**.
+ Used to store the minimum installer version required to install.
+ In the attribute `Package/@InstallerVersion`.
+- **Word Count**.
+ A bit field in attributes `Package/@ShortNames`, `Package/@Compressed`, `Package/@AdminImage`, and `Package/@InstallPrivileges`.
+- **Creating Application**.
+ Set by the linker to "Windows Installer XML" and a version number.
+- **Security**.
+ A bit field in attribute `Package/@ReadOnly`.
+
+\par Localization of the WiX UI extension.
+
+We are using the WiX UI extension for the installer.
+It already has localizations for many languages,
+ including most of the ones that we already support in the plugin.
+There are a few, however, that do not have localizations, yet, and we'll have to provide them.
+The source code location for these WiX localizations is `src/ext/UIExtension/wixlib`
+ with files named `WixUI_.wxl`.
+These lists were checked against both WiX 3.7 and WiX 3.8.
+
+The WiX UI extension DOES NOT support the following languages that we do.
+
+- fil-PH. Filipino - Phillipines.
+- kn-IN. Kanada - India.
+- mr-IN. Maratha - India.
+- ms. Malaysian - generic.
+- nn-NO. Nynorsk - Norway.
+- ur-PK. Urdu - Pakistan.
+
+The WiX UI extension supports the following languages that we do not yet support.
+
+- kk-KZ. Kazakh - Kazakhstan.
+- ko-KR. Korean - Korea.
+- lt-LT. Lithuanian - Lithuania.
+- lv-LV. Latvian - Latvia.
+- sl-SI. Slovenian - Slovenia.
+- sr-Latn-CS. Serbian, Latin alphabet - Serbia and Montenegro.
+- zh-HK. China - Hong Kong
+
+*/
\ No newline at end of file
Index: installer/src/innosetup-exe/64BitTwoArch.iss
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/innosetup-exe/64BitTwoArch.iss
@@ -0,0 +1,60 @@
+[Setup]
+AppName=Adblock Plus IE
+AppVersion={#version}
+CreateAppDir=No
+DisableStartupPrompt=Yes
+DisableDirPage=Yes
+DisableProgramGroupPage=Yes
+DisableReadyPage=Yes
+DisableFinishedPage=Yes
+DisableWelcomePage=Yes
+Uninstallable=No
+ArchitecturesInstallIn64BitMode=x64
+OutputDir=..\..\build
+OutputBaseFilename=adblockplusie-{#version}
+SignTool=signtool
+
+[Files]
+; Install adblockplusie-FINAL-x64.msi if running in 64-bit mode,
+; adblockplusie-FINAL-ia32.msi otherwise.
+Source: "..\..\build\x64\adblockplusie-{#version}-multilanguage-x64.msi"; DestDir: "{tmp}"; Check: Is64BitInstallMode
+Source: "..\..\build\ia32\adblockplusie-{#version}-multilanguage-ia32.msi"; DestDir: "{tmp}"; Check: not Is64BitInstallMode
+
+[Run]
+Filename: "msiexec.exe"; Parameters: "/i ""{tmp}\adblockplusie-{#version}-multilanguage-x64.msi"""; Check: Is64BitInstallMode
+Filename: "msiexec.exe"; Parameters: "/i ""{tmp}\adblockplusie-{#version}-multilanguage-ia32.msi"""; Check: not Is64BitInstallMode
+
+[Code]
+// Make sure InnoSetup always runs in silent mode, the UI is provided by the
+// MSI. Origin of the code is https://stackoverflow.com/a/21577388/785541.
+#ifdef UNICODE
+ #define AW "W"
+#else
+ #define AW "A"
+#endif
+type
+ HINSTANCE = THandle;
+
+function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;
+ lpParameters: string; lpDirectory: string; nShowCmd: Integer): HINSTANCE;
+ external 'ShellExecute{#AW}@shell32.dll stdcall';
+
+function InitializeSetup: Boolean;
+begin
+ // if this instance of the setup is not silent which is by running
+ // setup binary without /SILENT parameter, stop the initialization
+ Result := WizardSilent;
+ // if this instance is not silent, then...
+ if not Result then
+ begin
+ // re-run the setup with /SILENT parameter; because executing of
+ // the setup loader is not possible with ShellExec function, we
+ // need to use a WinAPI workaround
+ if ShellExecute(0, '', ExpandConstant('{srcexe}'), '/SILENT', '',
+ SW_SHOW) <= 32
+ then
+ // if re-running this setup to silent mode failed, let's allow
+ // this non-silent setup to be run
+ Result := True;
+ end;
+end;
Index: installer/src/installer-lib/DLL.cpp
===================================================================
--- a/installer/src/installer-lib/DLL.cpp
+++ b/installer/src/installer-lib/DLL.cpp
@@ -1,13 +1,12 @@
/**
- * \file abp_ca.cpp Top-level source for custom actions. Includes DLL initialization.
- */
+* \file abp_ca.cpp Top-level source for custom actions. Includes DLL initialization.
+*/
#include "DLL.h"
#include
std::shared_ptr< DLL_Module > DLL_Module::singleton = 0 ;
-DLL_Module &
-DLL_Module::module()
+DLL_Module & DLL_Module::module()
{
if ( singleton )
{
@@ -17,11 +16,10 @@
}
/**
- * The attachment function is the _de facto_ equivalent of initialization. Under ordinary circumstances, this should
- * only be called once, and called before everything else.
- */
-void
-DLL_Module::attach( HINSTANCE handle )
+* The attachment function is the _de facto_ equivalent of initialization. Under ordinary circumstances, this should
+* only be called once, and called before everything else.
+*/
+void DLL_Module::attach( HINSTANCE handle )
{
if ( singleton )
{
@@ -31,25 +29,23 @@
}
/**
- * The detachment function is the _de facto_ equivalent of finalization. Under ordinary circumstances, this should
- * only be called once, and called after everything else.
- */
-void
-DLL_Module::detach()
+* The detachment function is the _de facto_ equivalent of finalization. Under ordinary circumstances, this should
+* only be called once, and called after everything else.
+*/
+void DLL_Module::detach()
{
singleton.reset();
}
/**
- * Since this class is mostly a replacement for a global variable, it's no surprising the constructor is almost trivial.
- */
+* Since this class is mostly a replacement for a global variable, it's no surprising the constructor is almost trivial.
+*/
DLL_Module::DLL_Module( HINSTANCE handle )
: handle( handle )
{
}
-std::wstring
-DLL_Module::name()
+std::wstring DLL_Module::name()
{
if ( _name )
return *_name ;
Index: installer/src/installer-lib/DLL.h
===================================================================
--- a/installer/src/installer-lib/DLL.h
+++ b/installer/src/installer-lib/DLL.h
@@ -1,6 +1,6 @@
/**
- * \file DLL.h The DLL as a Windows system module.
- */
+* \file DLL.h The DLL as a Windows system module.
+*/
#ifndef DLL_H
#define DLL_H
@@ -11,54 +11,54 @@
#include "windows.h"
/**
- * Singleton representing the DLL module. This class is the source of the file name for the custom action library, used in logging.
- * The choice to use a singleton reflects the design of the Windows API, which treats the module handle as a global for the DLL instance,
- * only appearing during the calls that manage the lifetime of the DLL.
- */
+* Singleton representing the DLL module. This class is the source of the file name for the custom action library, used in logging.
+* The choice to use a singleton reflects the design of the Windows API, which treats the module handle as a global for the DLL instance,
+* only appearing during the calls that manage the lifetime of the DLL.
+*/
class DLL_Module
{
public:
/**
- * Accessor function for the singleton.
- */
+ * Accessor function for the singleton.
+ */
static DLL_Module & module();
/**
- * Hook function to call on DLL attach.
- */
+ * Hook function to call on DLL attach.
+ */
static void attach( HINSTANCE handle );
/**
- * Hook function to call on DLL detach.
- */
+ * Hook function to call on DLL detach.
+ */
static void detach();
/**
- * Textual name of the DLL as an OS module.
- */
+ * Textual name of the DLL as an OS module.
+ */
std::wstring name();
private:
/**
- * The singleton value.
- */
+ * The singleton value.
+ */
static std::shared_ptr< DLL_Module > singleton;
/**
- * Private constructor ensures use of accessor function only.
- */
+ * Private constructor ensures use of accessor function only.
+ */
DLL_Module( HINSTANCE handle );
/**
- * Windows handle for the instance of the DLL.
- */
+ * Windows handle for the instance of the DLL.
+ */
HINSTANCE handle;
/**
- * The text name of the module.
- *
- * Implemented as a smart pointer for deferred evaluation of the system call to get the module name.
- */
+ * The text name of the module.
+ *
+ * Implemented as a smart pointer for deferred evaluation of the system call to get the module name.
+ */
std::shared_ptr< std::wstring > _name;
};
Index: installer/src/installer-lib/custom-i18n.h
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/custom-i18n.h
@@ -0,0 +1,47 @@
+/**
+ * \file custom-i18n.h
+ */
+
+#ifndef CUSTOM_I18N_H
+#define CUSTOM_I18N_H
+
+#include
+
+//-------------------------------------------------------
+// Message box text
+//-------------------------------------------------------
+/**
+ * Accessor to localizable content for custom actions.
+ *
+ * This class requires that the MSI contain a custom table named "AbpUIText" in the MSI database.
+ * The WiX definition of that table is in the file "custom_i18n.wxi".
+ * Each custom action has the responsibility for defining its own rows within this table.
+ */
+class custom_message_text
+{
+ Database & db ;
+ const std::wstring component ;
+
+public:
+ custom_message_text( Database & db, const std::wstring component )
+ : db( db ), component( component )
+ {}
+
+ std::wstring text( const std::wstring id )
+ {
+ try {
+ View v( db, L"SELECT `content` FROM `AbpUIText` WHERE `component`=? and `id`=?" ) ;
+ Record arg( 2 ) ;
+ arg.assign_string( 1, component ) ;
+ arg.assign_string( 2, id.c_str() ) ;
+ Record r( v.first( arg ) ) ;
+ return r.value_string( 1 ) ;
+ }
+ catch( ... )
+ {
+ return L" " ;
+ }
+ }
+} ;
+
+#endif
Index: installer/src/installer-lib/custom-i18n.wxi
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/custom-i18n.wxi
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
Index: installer/src/installer-lib/database.cpp
===================================================================
--- a/installer/src/installer-lib/database.cpp
+++ b/installer/src/installer-lib/database.cpp
@@ -1,6 +1,6 @@
/**
- * \file database.h MSI database
- */
+* \file database.h MSI database
+*/
#include "database.h"
#include "msiquery.h"
@@ -8,22 +8,19 @@
//-----------------------------------------------------------------------------------------
// Database
//-----------------------------------------------------------------------------------------
-/**
- * \par Implementation Notes
- * An MSI database handle is an overloaded type, used both for installation databases and one opened outside an installation.
- * Hence this base constructor initializes with that handle.
- *
- * \sa MSDN "Obtaining a Database Handle"
- * http://msdn.microsoft.com/en-us/library/windows/desktop/aa370541(v=vs.85).aspx
- */
-Database::Database( MSIHANDLE handle )
- : handle( handle )
+msi_handle Database::open_view( const wchar_t * query )
{
-}
-
-Database::~Database()
-{
- MsiCloseHandle( handle ) ;
+ MSIHANDLE view_handle ;
+ UINT x = MsiDatabaseOpenView( handle, query, & view_handle ) ;
+ if ( x == ERROR_BAD_QUERY_SYNTAX )
+ {
+ throw windows_api_error( "MsiDatabaseOpenView", "ERROR_BAD_QUERY_SYNTAX" ) ;
+ }
+ else if ( x == ERROR_INVALID_HANDLE )
+ {
+ throw windows_api_error( "MsiDatabaseOpenView", "ERROR_INVALID_HANDLE" ) ;
+ }
+ return msi_handle( view_handle ) ;
}
//-----------------------------------------------------------------------------------------
@@ -31,28 +28,70 @@
//-----------------------------------------------------------------------------------------
/**
- * Helper function for Installation_Database constructor.
- *
- * \par Resource Allocator
- * Return value of this function, a handle, must be released in order to avoid a resource leak.
- * Passing it as an argument to the Database constructor is adequate.
- */
-MSIHANDLE get_active_database( Immediate_Session & session )
+* Helper function for Installation_Database constructor.
+*
+* \par Resource Allocator
+* Return value of this function, a handle, must be released in order to avoid a resource leak.
+* Passing it as an argument to the Database constructor is adequate.
+*/
+msi_handle get_active_database( Immediate_Session & session )
{
- MSIHANDLE h = MsiGetActiveDatabase( session.handle ) ;
+ MSIHANDLE h( MsiGetActiveDatabase( session.handle ) ) ;
if ( h == 0 )
{
- throw std::runtime_error( "Failed to retrieve active databases" ) ;
+ throw windows_api_error( "MsiGetActiveDatabase", 0 ) ;
}
- return h ;
+ return msi_handle( h ) ;
}
/**
- * \par Implementation Notes
- * The only thing this constructor needs to do is to initialize the base class.
- */
+* \par Implementation Notes
+* The only thing this constructor needs to do is to initialize the base class.
+*/
Installation_Database::Installation_Database( Immediate_Session & session )
: Database( get_active_database( session ) )
{
// empty body
-}
\ No newline at end of file
+} ;
+
+//-----------------------------------------------------------------------------------------
+// View
+//-----------------------------------------------------------------------------------------
+/**
+* Implementation function for View::first().
+*/
+void view_first_body( UINT x )
+{
+ if ( x != ERROR_SUCCESS )
+ {
+ throw windows_api_error( "MsiViewExecute", x ) ;
+ }
+}
+
+Record View::first()
+{
+ view_first_body( MsiViewExecute( _handle, 0 ) ) ;
+ return next() ;
+}
+
+Record View::first( Record & arguments )
+{
+ view_first_body( MsiViewExecute( _handle, arguments._handle ) ) ;
+ return next() ;
+}
+
+Record View::next()
+{
+ MSIHANDLE h ;
+ UINT x = MsiViewFetch( _handle, & h ) ;
+ if ( x == ERROR_NO_MORE_ITEMS )
+ {
+ return Record( Record::null_t() ) ;
+ }
+ else if ( x == ERROR_SUCCESS )
+ {
+ return Record( msi_handle( h ) ) ;
+ }
+ throw windows_api_error( "MsiViewFetch", x ) ;
+}
+
Index: installer/src/installer-lib/database.h
===================================================================
--- a/installer/src/installer-lib/database.h
+++ b/installer/src/installer-lib/database.h
@@ -1,74 +1,204 @@
/**
- * \file database.h MSI database
- */
+* \file database.h MSI database
+*/
#ifndef DATABASE_H
#define DATABASE_H
#include
-#include "windows.h"
-#include "msi.h"
+#include
+#include
+#include
+#include
+
+#include "installer-lib.h"
+#include "handle.h"
#include "session.h"
+// Forward declarations
+class View ;
+
+//-------------------------------------------------------
+// Database
+//-------------------------------------------------------
/**
- * A Windows Installer database as contained in an MSI file.
- *
- * The API for MSI databases is shared between installation and non-installation contexts.
- * Roughly speaking, outside an installation the database supports both read and write,
- * but inside an installation the database is read-only.
- * The life cycle functions are not shared, in addition.
- * Outside of these restrictions, however, the API is mostly common.
- * This class is the base class for the common API.
- * Subclasses provide public constructors and provide access to API calls not in common.
- */
+* A Windows Installer database as contained in an MSI file.
+*
+* The API for MSI databases is shared between installation and non-installation contexts.
+* Roughly speaking, outside an installation the database supports both read and write,
+* but inside an installation the database is read-only.
+* The life cycle functions are not shared, in addition.
+* Outside of these restrictions, however, the API is mostly common.
+* This class is the base class for the common API.
+* Subclasses provide public constructors and provide access to API calls not in common.
+*/
class Database
{
protected:
- /**
- * Protected constructor. Life cycle depends strongly on context.
- */
- Database( MSIHANDLE handle );
+ typedef handle< MSIHANDLE, Disallow_Null, MSI_Generic_Destruction > handle_type ;
/**
- * Destructor.
- */
- ~Database();
+ * Protected constructor.
+ *
+ * An MSI database handle is an overloaded type, used both for installation databases and one opened outside an installation.
+ * These database handles, while both databases, have different capabilities and are thus defined in subclasses.
+ * Each subclass has the responsibility for obtaining a database handle appropriate to its circumstance.
+ *
+ * \sa MSDN "Obtaining a Database Handle"
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa370541(v=vs.85).aspx
+ */
+ Database( MSIHANDLE handle )
+ : handle( handle )
+ {}
-protected:
/**
- */
- MSIHANDLE handle;
+ */
+ handle_type handle ;
private:
/**
- * Private copy constructor is declared but not defined.
- */
- Database( const Database & );
+ * Private copy constructor is declared but not defined.
+ */
+ Database( const Database & ) ;
/**
- * Private assignment operator is declared but not defined.
- */
+ * Private assignment operator is declared but not defined.
+ */
Database & operator=( const Database & ) ;
-};
+
+ /**
+ * Open a new view for this database.
+ *
+ * \param query
+ * An SQL query using the restricted MSI syntax
+ *
+ * \sa
+ * - MSDN [MsiDatabaseOpenView function](http://msdn.microsoft.com/en-us/library/aa370082%28v=vs.85%29.aspx)
+ */
+ msi_handle open_view( const wchar_t * query ) ;
+
+ friend class View ;
+} ;
/**
- * A Windows Installer database in an installation context.
- */
+* A Windows Installer database in an installation context.
+*/
class Installation_Database : public Database
{
public:
/**
- * The constructor of a database in an installation context has no arguments because the database is a part of that context.
- */
- Installation_Database( Immediate_Session & session );
-};
+ * The constructor of a database in an installation context has no arguments because the database is a part of that context.
+ */
+ Installation_Database( Immediate_Session & session ) ;
+} ;
+//-------------------------------------------------------
+//
+//-------------------------------------------------------
/**
- * A Windows Installer database in a non-installation context.
- */
-class Non_Installation_Database : public Database
+* A Windows Installer database outside of an installation context, opened as a file from the file system.
+*
+* This is a read-only version of a file-system database.
+* Refactor the class to obtain other open-modes.
+*
+*/
+class File_System_Database : public Database
{
-};
+ /**
+ * Open function is separate to enable initializing base class before constructor body.
+ *
+ * \sa
+ * - MSDN [MsiOpenDatabase function](http://msdn.microsoft.com/en-us/library/aa370338%28v=vs.85%29.aspx)
+ */
+ msi_handle handle_from_pathname( const wchar_t * pathname )
+ {
+ MSIHANDLE handle ;
+ UINT x = MsiOpenDatabaseW( pathname, MSIDBOPEN_READONLY, & handle ) ;
+ if ( x != ERROR_SUCCESS )
+ {
+ throw windows_api_error( "MsiOpenDatabaseW", x, "MSI database on file system" ) ;
+ }
+ return msi_handle( handle ) ;
+ }
+
+public:
+ File_System_Database( const wchar_t * pathname )
+ : Database( handle_from_pathname( pathname ) )
+ {}
+} ;
+
+//-------------------------------------------------------
+// View
+//-------------------------------------------------------
+/*
+* The MSI database is accessible through a cut-down version of SQL.
+* There's no distinction between view and query in this dialect.
+*
+* \sa
+* - MSDN [Working with Queries](http://msdn.microsoft.com/en-us/library/aa372879%28v=vs.85%29.aspx)
+*/
+class View
+{
+ /**
+ * Policy class to close a view handle.
+ * View handles don't get closed with the generic close function for other MSI handles.
+ */
+ template< class T >
+ struct View_Destruction
+ {
+ /**
+ * \sa MSDN [MsiViewClose function](http://msdn.microsoft.com/en-us/library/aa370510%28v=vs.85%29.aspx)
+ */
+ inline static void close( T handle )
+ {
+ ::MsiViewClose( handle ) ;
+ }
+ } ;
+
+ typedef handle< MSIHANDLE, Disallow_Null, View_Destruction > handle_type ;
+
+ /**
+ * Handle for the MSI view object
+ */
+ handle_type _handle;
+
+public:
+ /**
+ * Ordinary constructor
+ */
+ View( Database & db, wchar_t * query )
+ : _handle( db.open_view( query ) )
+ {}
+
+ /**
+ * Execute the query and return the first record in its results.
+ *
+ * \param arguments
+ * List of parameters to supply as the query arguments (question marks).
+ */
+ Record first( Record & arguments ) ;
+
+ /**
+ * Execute the query and return the first record in its results.
+ *
+ * With no arguments, this version of the function may only be used with a query that takes no arguments.
+ */
+ Record first() ;
+
+ /**
+ * Retrieve the next record.
+ */
+ Record next() ;
+
+ /**
+ * End marker
+ */
+ inline Record end()
+ {
+ return Record( Record::null_t() ) ;
+ }
+} ;
+
#endif
Index: installer/src/installer-lib/handle.h
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/handle.h
@@ -0,0 +1,276 @@
+/**
+* \file handle.h The "install session" is the context for all custom installation behavior.
+*/
+
+#ifndef HANDLE_H
+#define HANDLE_H
+
+#include "windows.h"
+#include "msi.h"
+
+//-------------------------------------------------------
+// msi_handle
+//-------------------------------------------------------
+/**
+* Disambiguation class holding an MSIHANDLE.
+*
+* We need constructors for Record that accept both handles and record counts.
+* Since the underlying type of a handle is integral, without its own type these constructors are ambiguous.
+*/
+class msi_handle
+{
+ MSIHANDLE _handle ;
+
+public:
+ /**
+ * Ordinary constructor is explicit to avoid inadvertent conversions.
+ */
+ explicit msi_handle( MSIHANDLE handle )
+ : _handle( handle )
+ {}
+
+ operator MSIHANDLE()
+ {
+ return _handle ;
+ }
+} ;
+
+//-------------------------------------------------------
+// Handle Policies
+//-------------------------------------------------------
+/**
+* Policy class that indicates that a raw handle may not be zero.
+*/
+template< class T >
+struct Disallow_Null
+{
+ inline static bool prohibited_always() { return true ; }
+ inline static bool prohibited_from_outside() { return true ; }
+} ;
+
+/**
+* Policy class that indicates that a raw handle may be zero only when constructed internally.
+*/
+template< class T >
+struct Special_Null
+{
+ inline static bool prohibited_always() { return false ; }
+ inline static bool prohibited_from_outside() { return true ; }
+} ;
+
+/**
+* Policy class that indicates that a raw handle is permitted to be zero.
+*/
+template< class T >
+struct Allow_Null
+{
+ inline static bool prohibited_always() { return false ; }
+ inline static bool prohibited_from_outside() { return false ; }
+} ;
+
+/**
+* Policy class that does not close a handle at all.
+*/
+template< class T >
+class No_Destruction
+{
+public:
+ inline static void close( T handle ) {} ;
+} ;
+
+/**
+* Policy class that closes an MSI handle when it goes out of scope.
+*/
+template< class T >
+class MSI_Generic_Destruction
+{
+public:
+ inline static void close( T handle )
+ {
+ MsiCloseHandle( handle ) ;
+ } ;
+} ;
+
+/**
+* Policy class that closes a Windows handle when it goes out of scope.
+*/
+template< class T >
+class Windows_Generic_Destruction
+{
+public:
+ inline static void close( T handle )
+ {
+ CloseHandle( handle ) ;
+ } ;
+} ;
+
+
+//-------------------------------------------------------
+// Handle
+//-------------------------------------------------------
+/**
+* Raw handle is the base class for the generic handle.
+*
+* Raw handles always allow null, so that generic handles may allow or disallow them as they please.
+*/
+template< class T >
+class handle_raw
+{
+protected:
+ /**
+ * The underlying handle.
+ *
+ * This is the only data member of the class.
+ * Everything else is for life cycle and type conversion.
+ */
+ T _handle ;
+
+ /**
+ * Basic constructor is protected to cede creation policy entirely to subclasses.
+ */
+ handle_raw( T _handle )
+ : _handle( _handle )
+ {}
+
+public:
+ /**
+ * Conversion operator to underlying handle type.
+ */
+ operator T()
+ {
+ return _handle ;
+ }
+
+ /**
+ * Error thrown when initialize or assigning a null handle against policy.
+ *
+ * Note that this error is a logic_error, not a runtime error.
+ * If it's against policy for a handle to be null, it's an error for the caller to try to make it null.
+ * Policy enforcment here is not a substitute for good error handling by the caller.
+ * In many cases, the caller ought to be throwing windows_api_error.
+ */
+ struct null_handle_error
+ : public std::logic_error
+ {
+ null_handle_error()
+ : std::logic_error( "May not initialize with null handle" )
+ {}
+ } ;
+} ;
+
+/*
+* Handle class
+*/
+template<
+ class T,
+ template class Null_Policy,
+ template class Destruction_Policy = No_Destruction
+>
+class handle
+ : public handle_raw< T >
+{
+ /**
+ * Copy constructor prohibited.
+ *
+ * This class represents an external resource and is responsible for its lifecycle.
+ * As such, the semantics here require a one-to-one match between instances and resource.
+ * Copy construction violates these semantics.
+ *
+ * \par Implementation
+ * Currently, declared private and not defined.
+ * Add "= delete" for C++11.
+ */
+ handle( handle & ) ;
+
+ /**
+ * Copy assignment not implemented.
+ *
+ * It's not used anywhere yet.
+ *
+ * \par Implementation
+ * Currently, declared private and not defined.
+ */
+ handle operator=( const handle & ) ;
+
+ /**
+ * Validate initial handle values, both for construction and reinitialization assignment.
+ */
+ T validate_handle( T handle )
+ {
+ if ( Null_Policy< T >::prohibited_from_outside() && handle == 0 )
+ {
+ throw null_handle_error() ;
+ }
+ return handle ;
+ }
+
+protected:
+ /**
+ * Tag class for null record constructor
+ */
+ class null_t {} ;
+
+ /**
+ * Null handle constructor initializes its handle to zero.
+ *
+ * The null record constructor avoids the ordinary check that an external handle not be zero.
+ * It's declared protected so that it's not ordinarily visible.
+ * To use this constructor, derive from it and add a friend declaration.
+ */
+ handle( null_t )
+ : handle_raw( 0 )
+ {
+ if ( Null_Policy< T >::prohibited_always() )
+ {
+ throw null_handle_error() ;
+ }
+ }
+
+public:
+ /**
+ * Ordinary constructor.
+ *
+ * A check for a non-zero handle compiles in conditionally based on the Null_Policy.
+ */
+ handle( T handle )
+ : handle_raw( validate_handle( handle ) )
+ {}
+
+ /**
+ * Reinitialization Assignment.
+ *
+ * If we had C++11 move constructors, we wouldn't need this, since this acts exactly as construct-plus-move would.
+ */
+ handle & operator=( T handle )
+ {
+ validate_handle( handle ) ;
+ this -> ~handle() ;
+ _handle = handle ;
+ return * this ;
+ }
+
+ /**
+ * Destructor
+ *
+ * The check for a non-zero handle compiles out conditionally based on Null_Policy.
+ * The specific function used to close the handle is given by the Destruction_Policy.
+ */
+ ~handle()
+ {
+ if ( Null_Policy< T >::prohibited_always() || ( _handle != 0 ) ) {
+ Destruction_Policy< T >::close( _handle ) ;
+ }
+ }
+
+ /**
+ * Expose the underlying handle type.
+ */
+ typedef T handle_type ;
+} ;
+
+//-------------------------------------------------------
+// Common instantiations of handle
+//-------------------------------------------------------
+typedef handle< HANDLE, Disallow_Null, Windows_Generic_Destruction > Windows_Handle ;
+
+#endif
Index: installer/src/installer-lib/installer-lib.h
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/installer-lib.h
@@ -0,0 +1,114 @@
+/**
+* \file handle.h The "install session" is the context for all custom installation behavior.
+*/
+
+#ifndef INSTALLER_LIB_H
+#define INSTALLER_LIB_H
+
+#include
+#include
+
+/**
+* Standard runtime error for failure of Windows API calls.
+*
+* The design purpose of this class is to consistently report the details of a failed API call, with an eye toward logging.
+* All the arguments passed to the constructor appear in what().
+* In addition the return value of GetLastError() appears.
+*
+* All the types for the constructors are generic.
+* Any type that works with the output operator '<<' of a stream will work.
+*
+* \par Example
+* For a simple error, where there's not much to add over the API call and the error code itself, just omit the second argument.
+* \code
+* throw windows_api_error( "MsiDatabaseOpenView", "ERROR_BAD_QUERY_SYNTAX" )
+* \endcode
+*
+* \par
+* Sometimes you don't have a symbolic error code.
+* This example uses a numeric error and a clarifying message.
+* \code
+* throw windows_api_error( "MsiOpenDatabaseW", x, "MSI database is on file system" )
+* \endcode
+*/
+class windows_api_error
+ : public std::runtime_error
+{
+ template< class T1, class T2, class T3 >
+ static std::string make_message( T1 api_function, T2 error_code, T3 message )
+ {
+ std::ostringstream r, t ;
+ std::string s ;
+
+ t << api_function ;
+ s = t.str() ;
+ if ( s.empty() )
+ {
+ s = "" ;
+ }
+ r << s << " returned " ;
+
+ t = std::ostringstream() ;
+ t << error_code ;
+ s = t.str() ;
+ if ( s.empty() )
+ {
+ s = "" ;
+ }
+ r << s << " with last error code " << ::GetLastError() ;
+
+ t = std::ostringstream() ;
+ t << message ;
+ s = t.str() ;
+ if ( ! s.empty() )
+ {
+ r << ": " << s ;
+ }
+
+ return r.str() ;
+ }
+
+public:
+ /**
+ * Constructor with additional message.
+ *
+ * \param api_function
+ * The name of the API function that returned an error code or a null handle.
+ * \param error_code
+ * The error code that the function returned, either symbolic or numeric.
+ * Will be zero when the function returned a null handle.
+ * \param message
+ * Extra message to clarify the error
+ */
+ template< class T1, class T2, class T3 >
+ windows_api_error( T1 api_function, T2 error_code, T3 message )
+ : std::runtime_error( make_message( api_function, error_code, message ) )
+ {}
+
+ /**
+ * Constructor without anything extra.
+ *
+ * \param api_function
+ * The name of the API function that returned an error code or a null handle.
+ * \param error_code
+ * The error code that the function returned, either symbolic or numeric.
+ * Will be zero when the function returned a null handle.
+ */
+ template< class T1, class T2 >
+ windows_api_error( T1 api_function, T2 error_code )
+ : std::runtime_error( make_message( api_function, error_code, "" ) )
+ {}
+} ;
+
+/**
+*/
+class not_yet_supported
+ : public std::runtime_error
+{
+public:
+ not_yet_supported( std::string message )
+ : std::runtime_error( "Not yet supported: " + message )
+ {}
+} ;
+
+#endif
Index: installer/src/installer-lib/interaction.cpp
===================================================================
--- a/installer/src/installer-lib/interaction.cpp
+++ b/installer/src/installer-lib/interaction.cpp
@@ -1,13 +1,29 @@
/**
- * \file interaction.cpp Implementations of user interaction classes.
- */
+* \file interaction.cpp Implementations of user interaction classes.
+*/
#include "interaction.h"
/*
- * /sa MSDN "MsiProcessMessage function"
- * http://msdn.microsoft.com/en-us/library/windows/desktop/aa370354%28v=vs.85%29.aspx
- *
- * /sa MSDN "Sending Messages to Windows Installer Using MsiProcessMessage"
- * http://msdn.microsoft.com/en-us/library/windows/desktop/aa371614%28v=vs.85%29.aspx
- */
\ No newline at end of file
+* The two constructors are identical except for the type of argument 'message'
+* They rely on overloads of the Message constructor
+*/
+Installer_Message_Box::Installer_Message_Box(
+ std::wstring message,
+ box_type box,
+ buttonset_type buttonset,
+ default_button_type default_button,
+ icon_type icon
+ )
+ : Message( message, INSTALLMESSAGE( box | buttonset | default_button | icon ) )
+{}
+
+Installer_Message_Box::Installer_Message_Box(
+ std::string message,
+ box_type box,
+ buttonset_type buttonset,
+ default_button_type default_button,
+ icon_type icon
+ )
+ : Message( message, INSTALLMESSAGE( box | buttonset | default_button | icon ) )
+{}
Index: installer/src/installer-lib/interaction.h
===================================================================
--- a/installer/src/installer-lib/interaction.h
+++ b/installer/src/installer-lib/interaction.h
@@ -1,8 +1,104 @@
/**
- * \file interaction.h User interaction classes. Message boxes and translations.
- */
+* \file interaction.h User interaction classes. Message boxes and translations.
+*/
#ifndef INTERACTION_H
#define INTERACTION_H
+#include
+
+#include "session.h"
+
+#include
+#include
+#include
+
+/**
+* A modal dialog box as displayable from within a custom action.
+*
+* The only fully user interface element that the Windows Installer supports for use within custom actions is a small set of modal dialog boxes.
+* The Windows Installer provides the call MsiProcessMessage, overloaded by a set of message type constants.
+* This class represents those messages with user-provided messages; these ultimately call MessageBox.
+*
+* \sa
+* * MSDN [MsiProcessMessage function](http://msdn.microsoft.com/en-us/library/windows/desktop/aa370354%28v=vs.85%29.aspx)
+* * MSDN [Sending Messages to Windows Installer Using MsiProcessMessage](http://msdn.microsoft.com/en-us/library/windows/desktop/aa371614%28v=vs.85%29.aspx)
+*/
+class Installer_Message_Box
+ : public Message
+{
+public:
+ typedef enum
+ {
+ default_box = 0,
+ error_box = INSTALLMESSAGE::INSTALLMESSAGE_ERROR,
+ warning_box = INSTALLMESSAGE::INSTALLMESSAGE_WARNING,
+ user_box = INSTALLMESSAGE::INSTALLMESSAGE_USER
+ }
+ box_type ;
+
+ typedef enum
+ {
+ default_buttonset = 0,
+ ok = MB_OK,
+ ok_cancel = MB_OKCANCEL,
+ abort_retry_ignore = MB_ABORTRETRYIGNORE,
+ yes_no_cancel = MB_YESNOCANCEL,
+ yes_no = MB_YESNO,
+ retry_cancel = MB_RETRYCANCEL
+ }
+ buttonset_type ;
+
+ typedef enum
+ {
+ default_default_button = 0, ///< use the default button
+ default_button_one = MB_DEFBUTTON1,
+ default_button_two = MB_DEFBUTTON2,
+ default_button_three = MB_DEFBUTTON3
+ }
+ default_button_type ;
+
+ typedef enum
+ {
+ default_icon = 0, ///< use the default icon associated with the box_type
+ warning_icon = MB_ICONWARNING, ///< exclamation point
+ information_icon = MB_ICONINFORMATION, ///< lowercase letter "i" in a circle
+ error_icon = MB_ICONERROR ///< stop sign
+ }
+ icon_type ;
+
+ /**
+ * Ordinary constructor, wide string
+ */
+ Installer_Message_Box(
+ std::wstring message,
+ box_type box = box_type::user_box,
+ buttonset_type buttonset = buttonset_type::default_buttonset,
+ default_button_type default_button = default_button_type::default_default_button,
+ icon_type icon = icon_type::default_icon
+ ) ;
+
+ /**
+ * Ordinary constructor, regular string
+ */
+ Installer_Message_Box(
+ std::string message,
+ box_type box = box_type::user_box,
+ buttonset_type buttonset = buttonset_type::default_buttonset,
+ default_button_type default_button = default_button_type::default_default_button,
+ icon_type icon = icon_type::default_icon
+ ) ;
+} ;
+
+/**
+* Error for any non-handled return value from Session.write_message().
+*/
+struct unexpected_return_value_from_message_box
+ : std::logic_error
+{
+ unexpected_return_value_from_message_box()
+ : std::logic_error( "Unexpected return value from message box." )
+ {}
+} ;
+
#endif
Index: installer/src/installer-lib/process.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/process.cpp
@@ -0,0 +1,301 @@
+#include
+#include
+#include
+// is C++11, but implemented in VS2012
+#include
+
+#include "installer-lib.h"
+#include "process.h"
+
+//-------------------------------------------------------
+//-------------------------------------------------------
+bool process_by_any_exe_with_any_module::operator()( const PROCESSENTRY32W & process )
+{
+ if (processNames.find(process.szExeFile) != processNames.end())
+ {
+ if (moduleNames.empty())
+ return true;
+
+ Module_Snapshot ms(process.th32ProcessID);
+ const MODULEENTRY32W* me = ms.first();
+ while (me != 0)
+ {
+ if (moduleNames.find(me->szModule) != moduleNames.end())
+ {
+ return true;
+ }
+ me = ms.next();
+ }
+ }
+ return false;
+}
+
+//-------------------------------------------------------
+// creator_process
+//-------------------------------------------------------
+DWORD creator_process( HWND window )
+{
+ DWORD pid ;
+ DWORD r = GetWindowThreadProcessId( window, & pid ) ;
+ if ( r == 0 )
+ {
+ // Assert GetWindowThreadProcessId returned an error
+ // If the window handle is invalid, we end up here.
+ throw windows_api_error( "GetWindowThreadProcessId", r ) ;
+ }
+ return pid ;
+}
+
+//-------------------------------------------------------
+// send_message, send_endsession_messages
+//-------------------------------------------------------
+/**
+* Default process exit wait time (per message) 5000 ms
+*
+* 5 seconds is time that the system will wait before it considers a process non-responsive.
+*/
+static const unsigned int timeout = 5000 ; // milliseconds
+
+/**
+* An function object to process the results of sending window messages in send_message.
+*
+* We are using send_message within a system iteration over windows.
+* The system has its own convention for continuing/breaking the iteration.
+* This convention is assured consistently in send_message, which also provides default behavior.
+* This class provides the base for any variation from the default behavior.
+*/
+struct message_accumulator
+ : public std::binary_function< DWORD_PTR, bool, bool >
+{
+ virtual result_type operator()( first_argument_type result, second_argument_type return_value ) = 0 ;
+ virtual ~message_accumulator() {} ;
+} ;
+
+/**
+* Iteration action to send a message to a window and accumulate results.
+*
+* An error sending the message is not a failure for the function a whole.
+* The goal is to close the process, and if the window is no longer present, then the process may have already closed.
+* Therefore, we're ignoring both the return value and the result.
+*/
+class send_message
+{
+ UINT message ; ///< Message type for windows message
+ WPARAM p1 ; ///< Generic parameter 1 for windows message
+ LPARAM p2 ; ///< Generic parameter 2 for windows message
+ message_accumulator * f ; ///< Processor for results of sending the message.
+
+public:
+ /**
+ * Full contructor gathers message parameters and a message accumulator.
+ */
+ send_message( UINT message, WPARAM p1, LPARAM p2, message_accumulator & f )
+ : message( message ), p1( p1 ), p2( p2 ), f( & f )
+ {}
+
+ /**
+ * Abbreviated contructor gathers only message parameters.
+ * The message accumulator is absent.
+ */
+ send_message( UINT message, WPARAM p1, LPARAM p2 )
+ : message( message ), p1( p1 ), p2( p2 ), f( 0 )
+ {}
+
+ /*
+ * Enumeration function applied to each window.
+ */
+ bool operator()( HWND window )
+ {
+ DWORD_PTR result ;
+ LRESULT rv = SendMessageTimeoutW( window, message, p1, p2, SMTO_BLOCK, timeout, & result ) ;
+ /*
+ * If we have no message accumulator, the default behavior is to iterate everything.
+ * If we do have one, we delegate to it the decision whether to break or to continue.
+ */
+ if ( ! f )
+ {
+ return true ;
+ }
+ return ( * f )( result, (rv != 0) ) ;
+ }
+} ;
+
+/**
+* Send WM_QUERYENDSESSION and WM_ENDSESSION to a window.
+*
+* This window processor tries to shut down each application individually.
+* The alternative, gathering all the query results first and only then ending sessions, cannot be done with a single window enumeration.
+*/
+class send_endsession_messages
+{
+public:
+ /*
+ * Enumeration function applied to each window.
+ */
+ bool operator()( HWND window )
+ {
+ DWORD_PTR result ;
+ if ( ! SendMessageTimeoutW( window, WM_QUERYENDSESSION, 0, ENDSESSION_CLOSEAPP, SMTO_BLOCK, timeout, & result ) )
+ {
+ // Assert sending the message failed
+ // Ignore failure, just as with send_message().
+ return true ;
+ }
+ // Assert result is FALSE if the process has refused notice that it should shut down.
+ if ( ! result )
+ {
+ /*
+ * Returning false terminates iteration over windows.
+ * Since this process is refusing to shut down, we can't close all the processes and the operation fails.
+ */
+ return false ;
+ }
+ SendMessageTimeoutW( window, WM_ENDSESSION, 0, ENDSESSION_CLOSEAPP, SMTO_BLOCK, timeout, 0 ) ;
+ return true ;
+ }
+} ;
+
+/**
+* Accumulator for query-endsession message.
+*
+* Implements a conditional-conjunction of the query results.
+* All answers must be true in order for this result to be true,
+* and the calculation is terminated at the first answer 'false'.
+* As usual, errors sending messages are ignored.
+*/
+struct endsession_accumulator :
+ public message_accumulator
+{
+ bool permit_end_session ; ///< Accumulator variable yields final result.
+
+ /**
+ * Enumeration function applied to each window.
+ */
+ bool operator()( DWORD_PTR result, bool return_value )
+ {
+ if ( ( ! return_value ) || result )
+ {
+ // 1. If the result is true, then the process will permit WM_ENDSESSION
+ // 2. An error sending the message counts as "no new information"
+ return true ;
+ }
+ // The first false is the result of the calculation.
+ // The second false means to terminate enumeration early.
+ permit_end_session = false ;
+ return false ;
+ }
+
+ /**
+ * Ordinary constructor.
+ */
+ endsession_accumulator()
+ : permit_end_session( true )
+ {}
+} ;
+
+//-------------------------------------------------------
+// Process_Closer
+//-------------------------------------------------------
+/**
+* Shut down all the processes in the pid_list.
+*
+* The method used here uses blocking system calls to send messages to target processes.
+* Message processing delays, therefore, are sequential and the total delay is their sum.
+* Windows has non-blocking message calls available, and using a multi-threaded implementation would shorten that delay.
+* The code, hwoever, is significantly simpler without multi-threading.
+* The present use of this method is not closing dozens of applications, so delay performance is not critical.
+*
+* \return
+* The negation of is_running.
+* If is_running() was true at the beginning, then this function will have run refresh() before returning.
+*
+* \sa
+* - MSDN [WM_QUERYENDSESSION message](http://msdn.microsoft.com/en-us/library/windows/desktop/aa376890%28v=vs.85%29.aspx)
+* - MSDN [WM_ENDSESSION message](http://msdn.microsoft.com/en-us/library/windows/desktop/aa376889%28v=vs.85%29.aspx)
+*/
+bool Process_Closer::shut_down()
+{
+ /*
+ * If we're not running, we don't need to shut down.
+ */
+ if ( ! is_running() )
+ {
+ return true ;
+ }
+
+ /*
+ * Shutting down is a structure as an escalating series of attempts to shut down.
+ * After each one, we wait to see if the shut down has completed.
+ * Even though we're using a blocking call to send messages, applications need not block before exiting.
+ * Internet Explorer, in particular, does not.
+ *
+ * Note that termination occurs inside the default case within the switch statement
+ */
+ for ( unsigned int stage = 1 ; ; ++ stage )
+ {
+ // Assert is_running()
+ switch( stage )
+ {
+ case 1 :
+ /*
+ * Send WM_QUERYENDSESSION to every admissible window.
+ * Send WM_ENDSESSION if all processes are ready to shut down.
+ * We try this technique first, since this allows an application to restore its application state when it starts up again.
+ */
+ {
+ endsession_accumulator acc ;
+ send_message m1( WM_QUERYENDSESSION, 0, ENDSESSION_CLOSEAPP, acc ) ;
+ iterate_our_windows( m1 ) ;
+
+ if ( acc.permit_end_session )
+ {
+ send_message m2( WM_ENDSESSION, 0, 0 ) ;
+ iterate_our_windows( m2 ) ;
+ }
+ }
+ break ;
+
+ case 2 :
+ {
+ /*
+ * Send WM_QUERYENDSESSION and WM_ENDSESSION to every admissible window singly, not accumulating results.
+ */
+ send_endsession_messages m ;
+ iterate_our_windows( m ) ;
+ }
+ break ;
+
+ case 3 :
+ {
+ /*
+ * Send WM_CLOSE to every admissible window.
+ */
+ send_message m( WM_CLOSE, 0, 0 ) ;
+ iterate_our_windows( m ) ;
+ }
+ break ;
+
+ default :
+ /*
+ * We're out of ways to try to shut down.
+ */
+ return false ;
+ }
+
+ /*
+ * Wait loop.
+ */
+ for ( unsigned int j = 0 ; j < 50 ; ++ j )
+ {
+ std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ) ;
+ refresh() ;
+ if ( ! is_running() )
+ {
+ return true ;
+ }
+ }
+ // Assert is_running()
+ }
+ // No control path leaves the for-loop.
+} ;
+
Index: installer/src/installer-lib/process.h
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/process.h
@@ -0,0 +1,707 @@
+/**
+ * \file process.h
+ */
+
+#ifndef PROCESS_H
+#define PROCESS_H
+
+#include "installer-lib.h"
+#include "handle.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+//-------------------------------------------------------
+// wstring_ci: case-insensitive wide string
+//-------------------------------------------------------
+
+/**
+ * Traits class for case-insensitive strings.
+ */
+template< class T >
+struct ci_traits: std::char_traits< T >
+{
+ static bool eq( T c1, T c2 )
+ {
+ return std::tolower( c1 ) == std::tolower( c2 ) ;
+ }
+
+ static bool lt( T c1, T c2 )
+ {
+ return std::tolower( c1 ) < std::tolower( c2 ) ;
+ }
+
+ /**
+ * Trait comparison function.
+ *
+ * Note that this is not a comparison of C-style strings.
+ * In particular, there's no concern over null characters '\0'.
+ * The argument 'n' is the minimum length of the two strings being compared.
+ * We may assume that the intervals p1[0..n) and p2[0..n) are both valid substrings.
+ */
+ static int compare( const T * p1, const T * p2, size_t n )
+ {
+ while ( n-- > 0 )
+ {
+ T l1 = std::tolower( * p1 ++ ) ;
+ T l2 = std::tolower( * p2 ++ ) ;
+ if ( l1 == l2 )
+ {
+ continue ;
+ }
+ return ( l1 < l2 ) ? -1 : +1 ;
+ }
+ return 0 ;
+ }
+} ;
+
+typedef std::basic_string< wchar_t, ci_traits< wchar_t > > wstring_ci ;
+
+//-------------------------------------------------------
+// file_name_set: case-insensitive wide-string set
+//-------------------------------------------------------
+struct file_name_set
+ : public std::set< wstring_ci >
+{
+ /**
+ * Empty set constructor.
+ */
+ file_name_set()
+ {}
+
+ /**
+ * Constructor initialization from an array.
+ */
+ template< size_t n_file_names >
+ file_name_set( const wchar_t * ( & file_name_list )[ n_file_names ] )
+ {
+ for ( unsigned int j = 0 ; j < n_file_names ; ++ j )
+ {
+ insert( wstring_ci( file_name_list[ j ] ) ) ;
+ }
+ }
+} ;
+
+//-------------------------------------------------------
+//-------------------------------------------------------
+/**
+ * Filter by process name. Comparison is case-insensitive. With ABP module loaded
+ */
+class process_by_any_exe_with_any_module
+ : public std::binary_function< PROCESSENTRY32W, file_name_set, bool >
+{
+ /**
+ * Set of file names from which to match candidate process names.
+ *
+ * This is a reference to, not a copy of, the set.
+ * The lifetime of this object must be subordinate to that of its referent.
+ * The set used to instantiate this class is a member of Process_Closer,
+ * and so also is this class.
+ * Hence the lifetimes are coterminous, and the reference is not problematic.
+ */
+ const file_name_set & processNames ;
+ const file_name_set & moduleNames;
+public:
+ bool operator()( const PROCESSENTRY32W & ) ;
+ process_by_any_exe_with_any_module( const file_name_set & names, const file_name_set & moduleNames )
+ : processNames( names ), moduleNames( moduleNames )
+ {}
+} ;
+
+
+//-------------------------------------------------------
+// Process utility functions.
+//-------------------------------------------------------
+/**
+ * A promiscuous filter admits everything.
+ */
+struct every_process
+ : public std::unary_function< PROCESSENTRY32W, bool >
+{
+ bool operator()( const PROCESSENTRY32W & ) { return true ; } ;
+} ;
+
+/**
+ * Extractor that copies the entire process structure.
+ */
+struct copy_all
+ : public std::unary_function< PROCESSENTRY32W, PROCESSENTRY32W >
+{
+ PROCESSENTRY32W operator()( const PROCESSENTRY32W & process ) { return process ; }
+} ;
+
+/**
+ * Extractor that copies only the PID.
+ */
+struct copy_PID
+ : public std::unary_function< PROCESSENTRY32W, DWORD >
+{
+ inline DWORD operator()( const PROCESSENTRY32W & process ) { return process.th32ProcessID ; }
+} ;
+
+/**
+ * Retrieve the process ID that created a window.
+ *
+ * Wrapper around GetWindowThreadProcessId.
+ * Converts an error return from the system call into an exception.
+ * The system call can also retrieve the creating thread; we ignore it.
+ *
+ * \param window
+ * Handle of the window
+ * \return
+ * ID of the process that created the argument window
+ *
+ * \sa
+ * MSDN [GetWindowThreadProcessId function](http://msdn.microsoft.com/en-us/library/windows/desktop/ms633522%28v=vs.85%29.aspx)
+ */
+DWORD creator_process( HWND window ) ;
+
+//-------------------------------------------------------
+// Snapshot
+//-------------------------------------------------------
+/**
+ * Traits class for snapshots of all processes on the system.
+ */
+struct Process_Snapshot_Traits
+{
+ /**
+ * The type of the data resulting from CreateToolhelp32Snapshot.
+ */
+ typedef PROCESSENTRY32W result_type ;
+
+ /**
+ * Flags used to call CreateToolhelp32Snapshot.
+ */
+ const static DWORD snapshot_flags = TH32CS_SNAPPROCESS ;
+
+ /**
+ * Wrapper for 'first' function for processes
+ */
+ static BOOL first( HANDLE arg1, LPPROCESSENTRY32 arg2 )
+ {
+ return ::Process32First( arg1, arg2 ) ;
+ }
+
+ /**
+ * Wrapper for 'next' function for processes
+ */
+ static BOOL next( HANDLE arg1, LPPROCESSENTRY32 arg2 )
+ {
+ return ::Process32Next( arg1, arg2 ) ;
+ }
+} ;
+
+/**
+ * Traits class for snapshots of all modules loaded by a process.
+ */
+struct Module_Snapshot_Traits
+{
+ /**
+ * The type of the data resulting from CreateToolhelp32Snapshot.
+ */
+ typedef MODULEENTRY32W result_type ;
+
+ /**
+ * Flags used to call CreateToolhelp32Snapshot.
+ */
+ const static DWORD snapshot_flags = TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32 ;
+
+ /**
+ * Wrapper for 'first' function for modules
+ */
+ static BOOL first( HANDLE arg1, LPMODULEENTRY32 arg2 )
+ {
+ return ::Module32First( arg1, arg2 ) ;
+ }
+
+ /**
+ * Wrapper for 'next' function for modules
+ */
+ static BOOL next( HANDLE arg1, LPMODULEENTRY32 arg2 )
+ {
+ return ::Module32Next( arg1, arg2 ) ;
+ }
+} ;
+
+/**
+ * A snapshot wrapping the results of CreateToolhelp32Snapshot system call.
+ *
+ * Unfortunately, we cannot provide standard iterator for this class.
+ * Standard iterators must be copy-constructible, which entails the possibility of multiple, coexisting iteration states.
+ * The iteration behavior provided by Process32First and Process32Next relies upon state held within the snapshot itself.
+ * Thus, there can be only one iterator at a time for the snapshot.
+ * The two requirements are not simultaneously satisfiable.
+ *
+ * Instead of a standard iterator, we provide a first() and next() functions wrapping the corresponding system calls.
+ *
+ * \par Implementation
+ *
+ * - MSDN [CreateToolhelp32Snapshot function](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682489%28v=vs.85%29.aspx)
+ * - MSDN [Process32First function](http://msdn.microsoft.com/en-us/library/windows/desktop/ms684834%28v=vs.85%29.aspx)
+ * - MSDN [Process32Next function](http://msdn.microsoft.com/en-us/library/windows/desktop/ms684836%28v=vs.85%29.aspx)
+ * - MSDN [PROCESSENTRY32 structure](http://msdn.microsoft.com/en-us/library/windows/desktop/ms684839%28v=vs.85%29.aspx)
+ *
+ * \par Design Note
+ * The traits class defines first() and next() functions instead of using function pointers.
+ * This arises from a limitation in the compiler.
+ * The system calls are declared 'WINAPI', which is a compiler-specific extension.
+ * That extension, however, does not go far enough to be able to declare a pointer with the same modifier.
+ * Hence the system calls must be called directly; they are wrapped in the trait functions.
+ */
+template< class Traits >
+class Snapshot
+{
+public:
+ /**
+ * Expose the result type from the traits class as our own.
+ */
+ typedef typename Traits::result_type result_type ;
+
+private:
+ /**
+ * Process ID argument for CreateToolhelp32Snapshot.
+ */
+ DWORD _id ;
+
+ /**
+ * Handle to the underlying snapshot.
+ */
+ Windows_Handle handle ;
+
+ /**
+ * Buffer for reading a single process entry out of the snapshot.
+ *
+ * This buffer is constant insofar as the code outside this class is concerned.
+ * The accessor functions first() and next() return pointers to constant result_type.
+ */
+ result_type buffer;
+
+ /**
+ * Copy constructor declared private and not defined.
+ *
+ * \par Implementation
+ * Add "= delete" for C++11.
+ */
+ Snapshot( const Snapshot & ) ;
+
+ /**
+ * Copy assignment declared private and not defined.
+ *
+ * \par Implementation
+ * Add "= delete" for C++11.
+ */
+ Snapshot operator=( const Snapshot & ) ;
+
+ /**
+ * Create a new snapshot and return its handle.
+ */
+ Windows_Handle::handle_type make_handle()
+ {
+ Windows_Handle::handle_type h = ::CreateToolhelp32Snapshot( Traits::snapshot_flags, _id ) ;
+ if ( h == INVALID_HANDLE_VALUE )
+ {
+ throw windows_api_error( "CreateToolhelp32Snapshot", "INVALID_HANDLE_VALUE" ) ;
+ }
+ return h ;
+ }
+
+protected:
+ /**
+ * Constructor takes a snapshot.
+ */
+ Snapshot( DWORD id )
+ : _id( id ), handle( make_handle() )
+ {
+ // The various result types all define 'dwSize' with the same semantics.
+ buffer.dwSize = sizeof( result_type ) ;
+ }
+
+public:
+ /**
+ * Reconstruct the current instance with a new system snapshot.
+ *
+ * This function uses reinitialization assignment in the Windows_Handle class,
+ * which takes care of closing the old handle.
+ */
+ void refresh()
+ {
+ handle = make_handle();
+ }
+
+ /**
+ * Retrieve the first snapshot item into our member buffer.
+ *
+ * \return
+ * Pointer to our member buffer if there was a first item
+ * 0 otherwise
+ *
+ * \par Design Note
+ * There's no error handling in the present version of this function.
+ * In part that's because the underlying system call returns either true or false, both of which are ordinarily valid answers.
+ * The trouble is that a false return is overloaded.
+ * It can mean either that (ordinary) there are no more items or (exceptional) the snapshot did not contain the right kind of item.
+ * GetLastError is no help here; it doesn't distinguish between these cases.
+ * The upshot is that we rely that our implementation calls the right functions on the snapshot,
+ * and so we ignore the case where we've passed bad arguments to the system call.
+ */
+ const result_type * first()
+ {
+ return Traits::first(handle, &buffer) ? &buffer : 0;
+ }
+
+ /**
+ * Retrieve the next snapshot item into our member buffer and return a pointer to it.
+ * begin() must have been called first.
+ *
+ * \return
+ * Pointer to our member buffer if there was a first item
+ * 0 otherwise
+ *
+ * \par Design Note
+ * See the Design Note for first(); the same considerations apply here.
+ */
+ const result_type * next()
+ {
+ return Traits::next(handle, &buffer) ? &buffer : 0;
+ }
+} ;
+
+/**
+ * A snapshot of all processes running on the system.
+ */
+struct Process_Snapshot
+ : public Snapshot< Process_Snapshot_Traits >
+{
+ Process_Snapshot()
+ : Snapshot( 0 )
+ {}
+} ;
+
+/**
+ * A snapshot of all modules loaded for a given process.
+ */
+struct Module_Snapshot
+ : public Snapshot< Module_Snapshot_Traits >
+{
+ Module_Snapshot( DWORD process_id )
+ : Snapshot( process_id )
+ {}
+} ;
+
+//-------------------------------------------------------
+// initialize_process_list
+//-------------------------------------------------------
+/**
+ * \tparam T The type into which a PROCESSENTRY32W struture is extracted.
+ * \tparam Admittance Function type for argument 'admit'
+ * \tparam Extractor Function type for argument 'extract'
+ * \param admit A unary predicate function class that determines what's included
+ * A process appears in the list only if the predicate returns true.
+ * The use of this predicate is analogous to that in std::copy_if.
+ * \param convert A conversion function that takes a PROCESSENTRY32W as input argument and returns an element of type T.
+ */
+template
+void initialize_process_list(std::vector& v, Process_Snapshot &snap, Admittance admit = Admittance(), Extractor extract = Extractor())
+{
+ const PROCESSENTRY32W* p = snap.first();
+ while (p != 0)
+ {
+ if (admit(*p ))
+ {
+ /*
+ * We don't have C++11 emplace_back, which can construct the element in place.
+ * Instead, we copy the return value of the converter.
+ */
+ v.push_back(extract(*p));
+ }
+ p = snap.next();
+ }
+};
+
+//-------------------------------------------------------
+// initialize_process_set
+//-------------------------------------------------------
+/**
+ * \tparam T The type into which a PROCESSENTRY32W struture is extracted.
+ * \tparam Admittance Function type for argument 'admit'
+ * \tparam Extractor Function type for argument 'extract'
+ * \param admit A unary predicate function class that determines what's included
+ * A process appears in the list only if the predicate returns true.
+ * The use of this predicate is analogous to that in std::copy_if.
+ * \param convert A conversion function that takes a PROCESSENTRY32W as input argument and returns an element of type T.
+ */
+template
+void initialize_process_set(std::set< T > & set, Process_Snapshot &snap, Admittance admit = Admittance(), Extractor extract = Extractor())
+{
+ const PROCESSENTRY32W* p = snap.first();
+ while (p != 0)
+ {
+ if (admit(*p))
+ {
+ set.insert(extract(*p));
+ }
+ p = snap.next();
+ }
+};
+
+//-------------------------------------------------------
+// enumerate_windows
+//-------------------------------------------------------
+
+/**
+ * States of a window enumeration.
+ */
+typedef enum
+{
+ started, ///< The iteration is currently running
+ normal, ///< Iteration terminated without error.
+ early, ///< Callback returned false and terminated iteration early.
+ exception, ///< Callback threw an exception and thereby terminated iteration.
+ error ///< Callback always return true but EnumWindows failed.
+} enumerate_windows_state ;
+
+/**
+ * Data to perform a window enumeration, shared between the main function and the callback function.
+ */
+template< class F >
+struct ew_data
+{
+ /**
+ * Function to be applied to each enumerated window.
+ */
+ F & f ;
+
+ /**
+ * Completion status of the enumeration.
+ */
+ enumerate_windows_state status ;
+
+ /**
+ * An exception to be transported across the callback.
+ *
+ * The enumerator and the callback are not guaranteed to share a call stack,
+ * nor need they even share compatible exception conventions,
+ * and might not even be in the same thread.
+ * Thus, if the applied function throws an exception,
+ * we catch it in the callback and re-throw it in the enumerator.
+ * This member holds such an exception.
+ *
+ * This member holds an exception only if 'status' has the value 'exception'.
+ * Otherwise it's a null pointer.
+ */
+ std::unique_ptr< std::exception > ee ;
+
+ /**
+ * Ordinary constructor.
+ */
+ ew_data( F & f )
+ : f( f ), status( started )
+ {}
+} ;
+
+/**
+ * Callback function for EnumWindows.
+ *
+ * This function provides two standard behaviors.
+ * It records early termination of the enumeration, should that happen by the applied function returning false.
+ * It captures any exception thrown for transport back to the enumerator.
+ */
+template< class F >
+BOOL CALLBACK enumeration_callback( HWND window, LPARAM x )
+{
+ // LPARAM is always the same size as a pointer
+ ew_data< F > * data = reinterpret_cast< ew_data< F > * >( x ) ;
+ /*
+ * Top-level try statement prevents exception from propagating back to system.
+ */
+ try
+ {
+ bool r = data -> f( window ) ;
+ if ( ! r )
+ {
+ data -> status = early ;
+ }
+ return r ;
+ }
+ catch ( std::exception e )
+ {
+ data -> ee = std::unique_ptr< std::exception >( new( std::nothrow ) std::exception( e ) ) ;
+ data -> status = exception ;
+ return FALSE ;
+ }
+ catch ( ... )
+ {
+ data -> ee = std::unique_ptr< std::exception >() ;
+ data -> status = exception ;
+ return FALSE ;
+ }
+}
+
+/**
+ * Enumerate windows, applying a function to each one.
+ */
+template< class F >
+bool enumerate_windows( F f )
+{
+ ew_data< F > data( f ) ;
+ BOOL x( ::EnumWindows( enumeration_callback< F >, reinterpret_cast< LPARAM >( & data ) ) ) ;
+ bool r ;
+ if ( data.status != started )
+ {
+ // Assert status was changed within the callback
+ if ( data.status == exception )
+ {
+ /*
+ * The callback threw an exception of some sort.
+ * We forward it to the extent we are able.
+ */
+ if ( data.ee )
+ {
+ throw * data.ee ;
+ }
+ else
+ {
+ throw std::runtime_error( "Unknown exception thrown in callback function." ) ;
+ }
+ }
+ r = false ;
+ }
+ else
+ {
+ if ( x )
+ {
+ data.status = normal ;
+ r = true ;
+ }
+ else
+ {
+ // Assert EnumWindows failed
+ data.status = error ;
+ r = false ;
+ }
+ }
+ return r ;
+}
+
+//-------------------------------------------------------
+// Process_Closer
+//-------------------------------------------------------
+class Process_Closer
+{
+ /**
+ * Set of process identifiers matching one of the executable names.
+ */
+ std::set< DWORD > pid_set ;
+
+ /**
+ * Set of executable names by which to filter.
+ *
+ * The argument of the filter constructor is a set by reference.
+ * Since it does not make a copy for itself, we define it as a class member to provide its allocation.
+ */
+ file_name_set process_names ;
+
+ /**
+ * Set of module (DLL) names by which to filter.
+ */
+ file_name_set module_names ;
+
+ process_by_any_exe_with_any_module filter ;
+
+ /**
+ * Copy function object copies just the process ID.
+ */
+ copy_PID copy ;
+
+ /**
+ * Snapshot of running processes.
+ */
+ Process_Snapshot & snapshot ;
+
+ void update()
+ {
+ initialize_process_set( pid_set, snapshot, filter, copy ) ;
+ } ;
+
+ template< class F >
+ class only_our_processes
+ {
+ Process_Closer & self ;
+
+ F f ;
+
+ public:
+ only_our_processes( Process_Closer & self, F f )
+ : f( f ), self( self )
+ {}
+
+ bool operator()( HWND window )
+ {
+ bool b ;
+ try
+ {
+ b = self.contains( creator_process( window ) ) ;
+ }
+ catch ( ... )
+ {
+ // ignore window handles that are no longer valid
+ return true ;
+ }
+ if ( ! b )
+ {
+ // Assert the process that created the window is not in our pid_set
+ return true ;
+ }
+ return f( window ) ;
+ }
+ } ;
+
+public:
+ template
+ Process_Closer(Process_Snapshot & snapshot, const wchar_t* (&file_name_list)[n_file_names], const wchar_t* (&module_name_list)[n_module_names])
+ : snapshot(snapshot), process_names(file_name_list), module_names(module_name_list), filter(process_names, module_names)
+ {
+ update() ;
+ }
+ template
+ Process_Closer(Process_Snapshot & snapshot, const wchar_t * (&file_name_list)[n_file_names])
+ : snapshot(snapshot), process_names(file_name_list), module_names(), filter(process_names, module_names)
+ {
+ update() ;
+ }
+
+ /**
+ * Refresh our state to match the snapshot state.
+ */
+ void refresh()
+ {
+ pid_set.clear() ;
+ update() ;
+ }
+
+ bool is_running() { return ! pid_set.empty() ; } ;
+
+ bool contains( DWORD pid ) const { return pid_set.find( pid ) != pid_set.end() ; } ;
+
+ template< class F >
+ bool iterate_our_windows( F f )
+ {
+ only_our_processes< F > g( * this, f ) ;
+ return enumerate_windows( g ) ;
+ }
+
+ /*
+ * Shut down every process in the pid_set.
+ */
+ bool shut_down() ;
+
+} ;
+
+#endif
Index: installer/src/installer-lib/property.cpp
===================================================================
--- a/installer/src/installer-lib/property.cpp
+++ b/installer/src/installer-lib/property.cpp
@@ -1,10 +1,11 @@
/**
- * \file property.cpp Implementation of Property class etc.
- */
+* \file property.cpp Implementation of Property class etc.
+*/
+#include "installer-lib.h"
#include "property.h"
#include "session.h"
-#include "msiquery.h"
+#include
#include
//-----------------------------------------------------------------------------------------
@@ -16,22 +17,21 @@
{}
/**
- * \par Implementation
- * The center of the implementation is the MsiGetProperty function.
- */
+* \par Implementation
+* The center of the implementation is the MsiGetProperty function.
+*/
Property::operator std::wstring() const
{
/*
- * The screwy logic below arises from how the API works.
- * MsiGetProperty insists on copying into your buffer, but you don't know how long that buffer needs to be in advance.
- * The first call gets the size, but also the actual value if it's short enough.
- * A second call, if necessary, gets the actual value after allocat
- */
- // We only need a modest fixed-size buffer here, because we handle arbitrary-length property values in a second step.
- // It has 'auto' allocation, so we don't want it too large.
- TCHAR buffer1[ 64 ] = { L'\0' } ;
- DWORD length = sizeof( buffer1 ) / sizeof( TCHAR ) ;
- switch ( MsiGetProperty( handle, name.c_str(), buffer1, & length ) )
+ * The first call gets the size, but also the actual value if it's short enough.
+ * A second call, if necessary, allocates a sufficiently-long buffer and then gets the full value.
+ * We use only a modest fixed-size buffer for the first step, because we handle arbitrary-length property values in a second step.
+ */
+ // This buffer allocates on the stack, so we don't want it too large; 64 characters is enough for most properties anyway.
+ WCHAR buffer1[ 64 ] = { L'\0' } ;
+ DWORD length = sizeof( buffer1 ) / sizeof( WCHAR ) ;
+ UINT x = MsiGetPropertyW( handle, name.c_str(), buffer1, & length ) ;
+ switch ( x )
{
case ERROR_SUCCESS:
// This call might succeed, which means the return value was short enough to fit into the buffer.
@@ -40,28 +40,30 @@
// Do nothing yet.
break ;
default:
- throw std::runtime_error( "Error getting property" ) ;
+ throw windows_api_error( "MsiGetPropertyW", x, "fixed buffer" ) ;
}
// Assert we received ERROR_MORE_DATA
// unique_ptr handles deallocation transparently
- std::unique_ptr< TCHAR[] > buffer2( new TCHAR[ length ] );
- switch ( MsiGetProperty( handle, name.c_str(), buffer2.get(), & length ) )
+ std::unique_ptr< WCHAR[] > buffer2( new WCHAR[ length ] ) ;
+ x = MsiGetPropertyW( handle, name.c_str(), buffer2.get(), & length ) ;
+ switch ( x )
{
case ERROR_SUCCESS:
return std::wstring( buffer2.get(), length ) ;
default:
- throw std::runtime_error( "Error getting property" ) ;
+ throw windows_api_error( "MsiGetPropertyW", x, "allocated buffer" ) ;
}
}
/**
- * \par Implementation
- * The center of the implementation is the MsiSetProperty function.
- */
+* \par Implementation
+* The center of the implementation is the MsiSetProperty function.
+*/
void Property::operator=( const std::wstring & value )
{
- if ( MsiSetProperty( handle, name.c_str(), value.c_str() ) != ERROR_SUCCESS )
+ UINT x = MsiSetPropertyW( handle, name.c_str(), value.c_str() ) ;
+ if ( x != ERROR_SUCCESS )
{
- throw std::runtime_error( "Error setting property" ) ;
+ throw windows_api_error( "MsiSetPropertyW", x ) ;
}
}
Index: installer/src/installer-lib/property.h
===================================================================
--- a/installer/src/installer-lib/property.h
+++ b/installer/src/installer-lib/property.h
@@ -1,6 +1,6 @@
/**
- * \file property.h Installer property, whether from a live installation session or directly from a package or installed product.
- */
+* \file property.h Installer property, whether from a live installation session or directly from a package or installed product.
+*/
#ifndef PROPERTY_H
#define PROPERTY_H
@@ -10,114 +10,114 @@
#include "msi.h"
/*
- * Forward declaration of Session class required to break what's otherwise a cyclic definition.
- */
+* Forward declaration of Session class required to break what's otherwise a cyclic definition.
+*/
class Session ;
/**
- * Class representing an MSI property.
- *
- * MSI properties arise from three places:
- * - Live installations (seen in custom actions)
- * - Packages (MSI files)
- * - Products (as installed on a machine)
- * All of these access an underlying MSI database at some remove, though the details vary.
- * The underlying API calls, MsiGetProperty and MsiSetProperty, are overloaded,
- * in the sense that they take a single handle regardless of what it represents.
- * Constructors for this class, therefore, require both a name and one of these places.
- *
- * Handles are not user-visible in this library by policy.
- * Therefore this class has no public constructors.
- * Constructors are private and made available to the classes surrounding a handle with a 'friend' declaration.
- * These class provide factory access to property objects.
- * We use the default copy constructor and assignment operator (both implicitly declared) to make the factory function work.
- *
- * The semantics of properties is that they always appear as defined.
- * Properties not explicitly defined are considered to have the empty string (zero-length) as their value.
- * The return values of the API functions, for example, do not have an error code of "property not found".
- *
- * Rather than getter/setter functions, this class allows Property instances to appear exactly as strings.
- * Instead of a getter, we provide a string conversion operator.
- * Instead of a setter, we provide an overloaded assignment operator.
- *
- * \remark
- * This class is specialized to std::wstring for property names and values.
- * A more general library class would have these as template arguments, whether on the class or on functions.
- *
- * \remark
- * The class makes a copy of the handle of the underlying object rather than keeping a reference to that object.
- * This approach has the drawback that the user must ensure that the underlying object remains open for the lifetime of one of its derived Property instances.
- * For single-threaded custom actions (the ordinary case), this is never a problem,
- * because the entry point constructs a Session that lasts the entire duration of the CA.
- * For other tools using the library, this may not be the case.
- * Nevertheless, for a typical case where the scope of a Property is a single function, there's no problem.
- *
- * \sa MSDN on Windows Installer Properties.
- */
+* Class representing an MSI property.
+*
+* MSI properties arise from three places:
+* - Live installations (seen in custom actions)
+* - Packages (MSI files)
+* - Products (as installed on a machine)
+* All of these access an underlying MSI database at some remove, though the details vary.
+* The underlying API calls, MsiGetProperty and MsiSetProperty, are overloaded,
+* in the sense that they take a single handle regardless of what it represents.
+* Constructors for this class, therefore, require both a name and one of these places.
+*
+* Handles are not user-visible in this library by policy.
+* Therefore this class has no public constructors.
+* Constructors are private and made available to the classes surrounding a handle with a 'friend' declaration.
+* These class provide factory access to property objects.
+* We use the default copy constructor and assignment operator (both implicitly declared) to make the factory function work.
+*
+* The semantics of properties is that they always appear as defined.
+* Properties not explicitly defined are considered to have the empty string (zero-length) as their value.
+* The return values of the API functions, for example, do not have an error code of "property not found".
+*
+* Rather than getter/setter functions, this class allows Property instances to appear exactly as strings.
+* Instead of a getter, we provide a string conversion operator.
+* Instead of a setter, we provide an overloaded assignment operator.
+*
+* \remark
+* This class is specialized to std::wstring for property names and values.
+* A more general library class would have these as template arguments, whether on the class or on functions.
+*
+* \remark
+* The class makes a copy of the handle of the underlying object rather than keeping a reference to that object.
+* This approach has the drawback that the user must ensure that the underlying object remains open for the lifetime of one of its derived Property instances.
+* For single-threaded custom actions (the ordinary case), this is never a problem,
+* because the entry point constructs a Session that lasts the entire duration of the CA.
+* For other tools using the library, this may not be the case.
+* Nevertheless, for a typical case where the scope of a Property is a single function, there's no problem.
+*
+* \sa MSDN on Windows Installer Properties.
+*/
class Property
{
public:
/**
- * Conversion operator to std::wstring provides rvalue access to the property.
- */
+ * Conversion operator to std::wstring provides rvalue access to the property.
+ */
operator std::wstring() const ;
/**
- * Assignment operator from std::wstring provides lvalue access to the property.
- *
- * \par[in] value
- * Value to be assigned to the property
- */
+ * Assignment operator from std::wstring provides lvalue access to the property.
+ *
+ * \par[in] value
+ * Value to be assigned to the property
+ */
void operator=( const std::wstring & value ) ;
/**
- * Constructor from a session.
- *
- * The Windows Installer API uses a single handle type for all kinds of sessions.
- * Deferred sessions, though, have access only to a limited set of property values.
- * It's the responsibility of the user to ensure that property names refer to properties that contain meaningful data.
- * As a result, this constructor has base Session class as an argument, and we use this argument for both immediate and deferred sessions.
- *
- * \sa MSDN "Obtaining Context Information for Deferred Execution Custom Actions"
- * http://msdn.microsoft.com/en-us/library/windows/desktop/aa370543%28v=vs.85%29.aspx
- * for a list of properties that are available to deferred custom actions.
- */
+ * Constructor from a session.
+ *
+ * The Windows Installer API uses a single handle type for all kinds of sessions.
+ * Deferred sessions, though, have access only to a limited set of property values.
+ * It's the responsibility of the user to ensure that property names refer to properties that contain meaningful data.
+ * As a result, this constructor has base Session class as an argument, and we use this argument for both immediate and deferred sessions.
+ *
+ * \sa MSDN "Obtaining Context Information for Deferred Execution Custom Actions"
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa370543%28v=vs.85%29.aspx
+ * for a list of properties that are available to deferred custom actions.
+ */
Property( Session & session, std::wstring name ) ;
private:
/**
- * Handle to the installation, product, or package.
- * Any of these is permissible; the API does not distinguish these as types.
- */
+ * Handle to the installation, product, or package.
+ * Any of these is permissible; the API does not distinguish these as types.
+ */
MSIHANDLE handle ;
/**
- * Name of the property.
- *
- * \sa http://msdn.microsoft.com/en-us/library/windows/desktop/aa371245%28v=vs.85%29.aspx for more on property names,
- * including valid syntax and the internal scoping that the installer uses.
- */
+ * Name of the property.
+ *
+ * \sa http://msdn.microsoft.com/en-us/library/windows/desktop/aa371245%28v=vs.85%29.aspx for more on property names,
+ * including valid syntax and the internal scoping that the installer uses.
+ */
std::wstring name ;
} ;
/*
- * We need a couple of ancillary addition operators to concatenate properties and constants strings.
- * While not strictly necessary, they eliminate the need for an explicit conversion operator.
- * The compiler needs a means to infer that "+" refers to string operations directly;
- * it doesn't search all possible chains of conversions to locate an operator.
- * Support isn't complete, as we're not declaring concatenation for characters nor for rvalue references (the other meaning of &&).
- */
+* We need a couple of ancillary addition operators to concatenate properties and constants strings.
+* While not strictly necessary, they eliminate the need for an explicit conversion operator.
+* The compiler needs a means to infer that "+" refers to string operations directly;
+* it doesn't search all possible chains of conversions to locate an operator.
+* Support isn't complete, as we're not declaring concatenation for characters nor for rvalue references (the other meaning of &&).
+*/
/**
- * Concatenation operator for a constant-string plus a property
- */
+* Concatenation operator for a constant-string plus a property
+*/
inline std::wstring operator+( const wchar_t * left, const Property & right )
{
return left + std::wstring( right ) ;
}
/**
- * Concatenation operator for a property and a constant-string
- */
+* Concatenation operator for a property and a constant-string
+*/
inline std::wstring operator+( const Property & left, const wchar_t * right )
{
return std::wstring( left ) + right ;
Index: installer/src/installer-lib/record.cpp
===================================================================
--- a/installer/src/installer-lib/record.cpp
+++ b/installer/src/installer-lib/record.cpp
@@ -1,7 +1,8 @@
/**
- * \file record.cpp Implementation of Session class.
- */
+* \file record.cpp Implementation of Record class.
+*/
+#include "installer-lib.h"
#include "record.h"
#include "msiquery.h"
@@ -9,22 +10,73 @@
// Record
//-----------------------------------------------------------------------------------------
Record::Record( unsigned int n_fields )
- : n_fields( n_fields )
{
_handle = MsiCreateRecord( n_fields ) ;
if ( ! _handle )
{
- throw std::runtime_error( "Failed to create record" ) ;
+ throw windows_api_error( "MsiCreateRecord", 0 ) ;
}
}
Record::~Record()
{
- MsiCloseHandle( _handle ) ;
+ if ( _handle != 0 )
+ {
+ MsiCloseHandle( _handle ) ;
+ }
}
-void
-Record::assign_string( unsigned int field_index, std::wstring value )
+void Record::only_non_null()
{
- MsiRecordSetString( _handle, field_index, value.c_str() ) ;
+ if ( _handle == 0 )
+ {
+ throw std::runtime_error( "Operation only permitted for non-null objects" ) ;
+ }
+}
+
+void Record::assign_string( unsigned int field_index, const char *value )
+{
+ only_non_null() ;
+ MsiRecordSetStringA( _handle, field_index, value ) ;
+}
+
+void Record::assign_string( unsigned int field_index, const wchar_t *value )
+{
+ only_non_null() ;
+ MsiRecordSetStringW( _handle, field_index, value ) ;
+}
+
+/**
+* \par Implementation
+* - MSDN [MsiRecordGetString](http://msdn.microsoft.com/en-us/library/aa370368%28v=vs.85%29.aspx)
+*/
+std::wstring Record::value_string( unsigned int field_index )
+{
+ static wchar_t initial_buffer[ 1024 ] = L"" ;
+ DWORD length = 1023 ; // one less than the buffer length to hold a terminating null character
+ UINT x = MsiRecordGetStringW( _handle, field_index, initial_buffer, & length ) ;
+ if ( x == ERROR_SUCCESS )
+ {
+ return std::wstring( initial_buffer ) ;
+ }
+ if ( x == ERROR_MORE_DATA )
+ {
+ // Future: handle longer strings.
+ /*
+ * The present custom action only uses this function for strings that appear in dialog boxes.
+ * A thousand characters is about a dozen lines of text, which is far more than enough.
+ */
+ throw not_yet_supported( "retrieving string values longer than 1023 from a record" ) ;
+ }
+ throw windows_api_error( "MsiRecordGetStringW", x ) ;
+}
+
+size_t Record::n_fields() const
+{
+ unsigned int x = MsiRecordGetFieldCount( _handle ) ;
+ if ( x == 0xFFFFFFFF )
+ {
+ throw windows_api_error( "MsiRecordGetFieldCount", x, "invalid handle" ) ;
+ }
+ return x ;
}
\ No newline at end of file
Index: installer/src/installer-lib/record.h
===================================================================
--- a/installer/src/installer-lib/record.h
+++ b/installer/src/installer-lib/record.h
@@ -1,72 +1,246 @@
/**
- * \file session.h The "install session" is the context for all custom installation behavior.
- */
+* \file record.h Definition of Record class.
+*/
#ifndef RECORD_H
#define RECORD_H
#include
-#include "windows.h"
-#include "msi.h"
+
+#include
+#include
+
+#include "handle.h"
+
+// Forward
+class View ;
/**
- * An abstract record entity.
- * It represents both records in the installation database and as argument vectors for API functions.
- *
- * The ordinary constructor creates a free-standing record.
- * It takes only the number of fields in the created record.
- * The fields of the record are dynamically typed according to how they're assigned.
- *
- * Other constructors encapsulate records that are bound to databases.
- *
- * \par Invariant
- * - _handle is not null
- * - _handle is represents an open record obtained from MsiCreateRecord
- *
- * \sa http://msdn.microsoft.com/en-us/library/windows/desktop/aa372881%28v=vs.85%29.aspx
- * Windows Installer on MSDN: "Working with Records"
- */
+* An abstract record entity.
+* It represents both records in the installation database and as argument vectors for API functions.
+*
+* The ordinary constructor creates a free-standing record.
+* It takes only the number of fields in the created record.
+* The fields of the record are dynamically typed according to how they're assigned.
+* Other constructors will be required to encapsulate records that are bound to databases.
+*
+* This class has exclusive-ownership semantics for the API handle to the record.
+* Every constructor has a postcondition that the _handle member points to an open record.
+* The destructor closes the record.
+* The copy constructor syntax is used as a move constructor (since no C++11 yet).
+* Analogously, copy assignment has move semantics.
+*
+* \par Invariant
+* - _handle is not null implies _handle points to a record open in the Windows Installer subsystem
+*
+* \sa http://msdn.microsoft.com/en-us/library/windows/desktop/aa372881%28v=vs.85%29.aspx
+* Windows Installer on MSDN: "Working with Records"
+*/
class Record {
+ /**
+ *
+ */
+ typedef handle< MSIHANDLE, Special_Null, MSI_Generic_Destruction > record_handle_type ;
+
+ /**
+ * The handle for the record as a Windows Installer resource.
+ */
+ MSIHANDLE _handle ;
+
+ /**
+ * Construct a record from its handle as returned by some MSI call.
+ */
+ Record( msi_handle handle )
+ : _handle( handle )
+ {}
+
+ /**
+ * Internal validation guard for operations that require a non-null handle.
+ *
+ * \post
+ * - if _handle is zero, throw an exception
+ * - if _handle is non-zero, nothing
+ */
+ void only_non_null() ;
+
+ /**
+ * Proxy class used to implement move semantics, prior to use of C++11.
+ *
+ * /sa
+ * - Wikibooks [More C++ Idioms/Move Constructor](http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor)
+ */
+ struct Proxy_Record
+ {
+ MSIHANDLE _handle ;
+
+ Proxy_Record( MSIHANDLE handle )
+ : _handle( handle )
+ {}
+ } ;
+
+ /**
+ * Tag class for null record constructor
+ */
+ class null_t {} ;
+
+ /**
+ * Null record constructor.
+ *
+ * The null record constructor avoids the ordinary check that an external handle not be zero.
+ * It's declared private so that only friends can instantiate them.
+ */
+ Record( null_t )
+ : _handle( 0 )
+ {}
+
+ /**
+ * View class needs access to constructor-from-handle.
+ */
+ friend class View ;
+
public:
/**
- * Ordinary constructor creates a free-standing record.
- * Use this for creating argument vectors.
- *
- * \param[in] n_fields
- * Number of fields in the created record.
- */
+ * Ordinary constructor creates a free-standing record.
+ * Use this for creating argument vectors.
+ *
+ * \post _handle points to a record obtained from MsiCreateRecord
+ *
+ * \param[in] n_fields
+ * Number of fields in the created record.
+ */
Record( unsigned int n_fields ) ;
/**
- * Destructor
- */
+ * Destructor
+ */
~Record() ;
/**
- * Assign a string to a record
- *
- * \param[in] field_index
- * Index into the record as a vector of fields
- * \param[in] value
- * String to write into the field
- */
- void assign_string( unsigned int field_index, std::wstring value ) ;
+ * Copy constructor syntax used as a move constructor.
+ */
+ Record( Record & r )
+ : _handle( r._handle )
+ {
+ r._handle = 0 ;
+ }
/**
- * Handle accessor.
- */
+ * Proxy move constructor.
+ */
+ Record( Proxy_Record r )
+ : _handle( r._handle )
+ {
+ r._handle = 0 ;
+ }
+
+ /**
+ * Copy assignment syntax has move assignment semantics.
+ */
+ Record & operator=( Record & r )
+ {
+ this -> ~Record() ;
+ _handle = r._handle ;
+ r._handle = 0 ;
+ return * this ;
+ }
+
+ /**
+ * Proxy move assignment.
+ */
+ Record & operator=( Proxy_Record pr )
+ {
+ this -> ~Record() ;
+ _handle = pr._handle ;
+ pr._handle = 0 ;
+ return * this ;
+ }
+
+ /**
+ * Proxy conversion operator
+ */
+ operator Proxy_Record()
+ {
+ Proxy_Record pr( _handle ) ;
+ _handle = 0 ;
+ return pr ;
+ }
+
+ /**
+ * Two records are equal exactly when their handles are equal.
+ */
+ inline bool operator==( const Record & x ) const
+ {
+ return _handle == x._handle ;
+ }
+
+ /**
+ * Standard inequality operator defined by negating the equality operator.
+ */
+ inline bool operator!=( const Record & x ) const
+ {
+ return ! operator==( x ) ;
+ }
+
+ /**
+ * Assign a string to a record, (regular) character pointer.
+ *
+ * \param[in] field_index
+ * Index into the record as a vector of fields
+ * \param[in] value
+ * String to write into the field
+ */
+ void assign_string( unsigned int field_index, const char *value ) ;
+
+ /**
+ * Assign a string to a record, regular string version.
+ *
+ * \param[in] field_index
+ * Index into the record as a vector of fields
+ * \param[in] value
+ * String to write into the field
+ */
+ void assign_string( unsigned int field_index, const std::string value )
+ {
+ assign_string( field_index, value.c_str() );
+ }
+
+ /**
+ * Assign a string to a record, wide character pointer version.
+ *
+ * \param[in] field_index
+ * Index into the record as a vector of fields
+ * \param[in] value
+ * String to write into the field
+ */
+ void assign_string( unsigned int field_index, const wchar_t *value ) ;
+
+ /**
+ * Assign a string to a record, wide string version.
+ *
+ * \param[in] field_index
+ * Index into the record as a vector of fields
+ * \param[in] value
+ * String to write into the field
+ */
+ void assign_string( unsigned int field_index, const std::wstring value )
+ {
+ assign_string( field_index, value.c_str() );
+ }
+
+ /**
+ * Retrieve a wide string value from a record
+ */
+ std::wstring value_string( unsigned int field_index ) ;
+
+ /**
+ * The number of fields in the record.
+ */
+ size_t n_fields() const ;
+
+ /**
+ * Handle accessor.
+ */
MSIHANDLE handle() { return _handle ; }
-
-private:
- /**
- * The handle for the record as a Windows Installer resource.
- */
- MSIHANDLE _handle ;
-
- /**
- * The number of fields in the record.
- */
- unsigned int n_fields ;
};
#endif
Index: installer/src/installer-lib/run-tests.cmd
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/run-tests.cmd
@@ -0,0 +1,7 @@
+@echo off
+rem Test runner for installer library unit test
+rem Switches current directory to location of test MSI database file
+pushd %~dp0
+cd ..\..\build\x64
+.\Debug\installer-ca-tests
+popd
\ No newline at end of file
Index: installer/src/installer-lib/session.cpp
===================================================================
--- a/installer/src/installer-lib/session.cpp
+++ b/installer/src/installer-lib/session.cpp
@@ -1,53 +1,96 @@
/**
- * \file session.cpp Implementation of Session class.
- */
+* \file session.cpp Implementation of Session class.
+*/
+#include "installer-lib.h"
#include "session.h"
#include "property.h"
#include "msiquery.h"
//-----------------------------------------------------------------------------------------
+// Message
+//-----------------------------------------------------------------------------------------
+Message::Message( std::string message, INSTALLMESSAGE message_type )
+ : r( 1 ), message_type( message_type )
+{
+ r.assign_string( 0, message ) ;
+}
+
+Message::Message( std::wstring message, INSTALLMESSAGE message_type )
+ : r( 1 ), message_type( message_type )
+{
+ r.assign_string( 0, message ) ;
+}
+
+//-----------------------------------------------------------------------------------------
// Session
//-----------------------------------------------------------------------------------------
-Session::Session( MSIHANDLE handle, std::wstring name )
+Session::Session( MSIHANDLE handle, std::string name )
: handle( handle ),
- log_prefix( name + L": " )
+ log_prefix( name + ": " )
{
- log( L"Entering custom action" ) ;
+ log_prefix_w.assign( name.begin(), name.end() ) ;
+ log_prefix_w += L": " ;
+ log_noexcept( "Entering custom action" ) ;
+}
+
+Session::~Session()
+{
+ log_noexcept( "Exiting custom action" ) ;
}
/**
- * \par Implementation Notes
- * The session handle doesn't need to be closed.
- * It's provided as an argument to the custom action at the outset, and we do not manage its life cycle.
- */
-Session::~Session()
+* A message for the installation log.
+*
+* Writing to the installation log uses MsiProcessMessage just like interactive dialog boxes do.
+*
+* This class is not exposed outside this compilation unit because everything it can do is already exposed by the log functions.
+*/
+struct Log_Message
+ : public Message
{
- log( L"Exiting custom action" ) ;
+ Log_Message ( std::wstring message )
+ : Message( message, INSTALLMESSAGE_INFO )
+ {}
+
+ Log_Message ( std::string message )
+ : Message( message, INSTALLMESSAGE_INFO )
+ {}
+} ;
+
+void Session::log( std::string message )
+{
+ write_message( Log_Message( log_prefix + message ) ) ;
}
-/**
- * \par Implementation Notes
- * To write to the installation log, we use a call to MsiProcessMessage with message type INSTALLMESSAGE_INFO.
- * The text to be written needs to go in a "record" (yes, a database record) that acts as an argument vector.
- * For the message type we're using, we need only a record with a single field.
- *
- * \sa MSDN "MsiProcessMessage function"
- * http://msdn.microsoft.com/en-us/library/windows/desktop/aa370354%28v=vs.85%29.aspx
- * MsiProcessMessage is mostly for user interaction with message boxes, but it's also the access to the installation log.
- */
void Session::log( std::wstring message )
{
- Record r = Record( 1 );
- r.assign_string( 0, log_prefix + message );
- int e = MsiProcessMessage( handle, INSTALLMESSAGE_INFO, r.handle() ) ;
- if ( e != IDOK )
- {
- throw std::runtime_error( "Did not succeed writing to log." ) ;
- }
+ write_message( Log_Message( log_prefix_w + message ) ) ;
}
-Immediate_Session::Immediate_Session( MSIHANDLE handle, std::wstring name )
+void Session::log_noexcept( std::string message )
+{
+ write_message_noexcept( Log_Message( log_prefix + message ) ) ;
+}
+
+int Session::write_message( Message & m )
+{
+ int x = write_message_noexcept( m ) ;
+ if ( x == -1 )
+ {
+ throw windows_api_error( "MsiProcessMessage", x, "attempt to write to log file" ) ;
+ }
+ return x ;
+}
+
+int Session::write_message_noexcept( Message & m )
+{
+ return MsiProcessMessage( handle, m.message_type, m.r.handle() ) ;
+}
+
+//-----------------------------------------------------------------------------------------
+// Immediate_Session
+//-----------------------------------------------------------------------------------------
+Immediate_Session::Immediate_Session( MSIHANDLE handle, std::string name )
: Session( handle, name )
-{
-}
+{}
Index: installer/src/installer-lib/session.h
===================================================================
--- a/installer/src/installer-lib/session.h
+++ b/installer/src/installer-lib/session.h
@@ -1,6 +1,6 @@
/**
- * \file session.h The "install session" is the context for all custom installation behavior.
- */
+* \file session.h The "install session" is the context for all custom installation behavior.
+*/
#ifndef SESSION_H
#define SESSION_H
@@ -13,73 +13,155 @@
#include "msi.h"
//-----------------------------------------------------------------------------------------
+// Message
+//-----------------------------------------------------------------------------------------
+/**
+* Wrapper class for arguments to MsiProcessMessage.
+*
+* The "user interface" for custom actions includes both interactive dialog boxes as well as the installation log.
+* All of them use the same call, MsiProcessMessage.
+* This class encapsulates its arguments.
+*
+* \sa
+* * MSDN [MsiProcessMessage function](http://msdn.microsoft.com/en-us/library/windows/desktop/aa370354%28v=vs.85%29.aspx)
+* * MSDN [Sending Messages to Windows Installer Using MsiProcessMessage](http://msdn.microsoft.com/en-us/library/windows/desktop/aa371614%28v=vs.85%29.aspx)
+*/
+class Message
+{
+protected:
+ /**
+ * The flags used by MsiProcessMessage as the box type.
+ */
+ INSTALLMESSAGE message_type ;
+
+ /**
+ * The record argument to MsiProcessMessage
+ */
+ Record r ;
+
+ Message( std::string message, INSTALLMESSAGE message_type ) ;
+
+ Message( std::wstring message, INSTALLMESSAGE message_type ) ;
+
+ /**
+ * This class is a helper for Session, mustering all the arguments for MsiProcessMessage except for the session handle.
+ */
+ friend Session ;
+} ;
+
+//-----------------------------------------------------------------------------------------
// Session
//-----------------------------------------------------------------------------------------
/**
- * A Windows Installer session
- *
- * Always instantiate an instance of this class at the start of each custom action.
- * Copy and assignment are disabled, so session objects are always passed by reference.
- *
- * This class is the base for both immediate and deferred custom actions.
- * Immediate custom actions always have an installer database associated with them; deferred actions never do.
- * Both immediate and deferred actions may be executed synchronously or asynchronously; this class is silent about any difference.
- *
- * \par Notes
- * This class is patterned after WcaInitialize/WcaFinalize of the WiX custom action library.
- * There are two things that class does that this one does not.
- * - Extract the file version information from the DLL using GetModuleFileName* and GetFileVersionInfo* system calls.
- * - Set a "global atom" (a Windows system object) to store the logging state, later to be accessed by deferred actions.
- */
+* A Windows Installer session
+*
+* Always instantiate an instance of this class at the start of each custom action.
+* Copy and assignment are disabled, so session objects are always passed by reference.
+*
+* This class is the base for both immediate and deferred custom actions.
+* Immediate custom actions always have an installer database associated with them; deferred actions never do.
+* Both immediate and deferred actions may be executed synchronously or asynchronously; this class is silent about any difference.
+*
+* \par Notes
+* This class is patterned after WcaInitialize/WcaFinalize of the WiX custom action library.
+* There are two things that class does that this one does not.
+* - Extract the file version information from the DLL using GetModuleFileName* and GetFileVersionInfo* system calls.
+* - Set a "global atom" (a Windows system object) to store the logging state, later to be accessed by deferred actions.
+*/
class Session {
public:
/**
- * Destructor.
- */
+ * Destructor.
+ */
~Session() ;
/**
- * Write a message to the installation log.
- */
+ * Write a message to the installation log, regular string version.
+ */
+ void log( std::string message ) ;
+
+ /**
+ * Write a message to the installation log, wide string version.
+ */
void log( std::wstring message ) ;
+ /**
+ * Write a message to the installation log without raising an exception.
+ *
+ * Use this function only in the three circumstances when an exception cannot be caught by an entry point catch-all.
+ * First and second, there's the constructor and destructor of a Session instance.
+ * These log entry into and exit from the custom action, respectively.
+ * Third, there's the top level catch-blocks of the CA.
+ * The scope of the Session object cannot be within the try-block in order for it to be in scope for the catch-block.
+ * The session must be in scope in the catch-block to allow logging error messages.
+ * In all other cases, use the exception mechanism.
+ */
+ void log_noexcept( std::string message ) ;
+
+ /**
+ * Write to a MessageBox dialog.
+ */
+ int write_message( Message & ) ;
+
protected:
/**
- * Ordinary constructor is protected; public constructors are all in subclasses.
- * The MSI system uses a single handle type for all types of sessions. This handle is here in this base class.
- *
- * \param[in] handle
- * Handle for the Windows Installer session provided as an argument to a custom action.
- * \param[in] name
- * The name of the custom action, used for logging.
- */
- Session( MSIHANDLE handle, std::wstring name ) ;
+ * Ordinary constructor is protected; public constructors are all in subclasses.
+ * The MSI system uses a single handle type for all types of sessions. This handle is here in this base class.
+ *
+ * \param[in] handle
+ * Handle for the Windows Installer session provided as an argument to a custom action.
+ * \param[in] name
+ * The name of the custom action, used for logging.
+ * This string must be ASCII characters only, so that its wide-character version displays identically.
+ */
+ Session( MSIHANDLE handle, std::string name ) ;
-protected:
/**
- * Handle for the Windows Installer session.
- */
+ * Handle for the Windows Installer session.
+ *
+ * The life cycle of the session handle is not the responsibility of the base class.
+ * In an interactive session, the handle is provided as an argument to the custom action entry point, and we do not manage its life cycle.
+ * In an offline session, the handle is created in the (subclass) constructor.
+ */
MSIHANDLE handle ;
private:
/**
- * Prefix for log messages. Contains the name of the custom action.
- */
- std::wstring log_prefix ;
+ * Prefix for log messages, regular string. Contains the name of the custom action.
+ */
+ std::string log_prefix ;
/**
- * Private copy constructor is declared but not defined.
- */
+ * Prefix for log messages, wide string. Contains the name of the custom action.
+ */
+ std::wstring log_prefix_w ;
+
+ /**
+ * Private copy constructor is declared but not defined.
+ *
+ * C++11: declare with = delete.
+ */
Session( const Session & ) ;
/**
- * Private assignment operator is declared but not defined.
- */
+ * Write a message with MsiProcessMessage and throw no exceptions.
+ *
+ * This is declared private because there are very few cases in which no-exception behavior is required.
+ *
+ * C++11: declare with **noexcept**.
+ */
+ int write_message_noexcept( Message & m ) ;
+
+ /**
+ * Private assignment operator is declared but not defined.
+ *
+ * C++11: declare with = delete.
+ */
Session & operator=( const Session & ) ;
/**
- * The Property class requires access to the session handle.
- */
+ * The Property class requires access to the session handle.
+ */
friend Property::Property( Session & session, std::wstring name ) ;
};
@@ -87,28 +169,30 @@
// Immediate_Session
//-----------------------------------------------------------------------------------------
/**
- * Session for immediate custom actions.
- *
- * Access to the installer database is by passing a reference to a class of this subtype to a Database constructor.
- */
+* Session for immediate custom actions.
+*
+* Access to the installer database is by passing a reference to a class of this subtype to a Database constructor.
+*/
class Immediate_Session : public Session
{
public:
/**
- * Ordinary constructor.
- *
- * \param[in] handle
- * Handle for the Windows Installer session provided as an argument to a custom action.
- * \param[in] name
- * The name of the custom action, used for logging.
- */
- Immediate_Session( MSIHANDLE handle, std::wstring name ) ;
+ * Ordinary constructor.
+ *
+ * \param[in] handle
+ * Handle for the Windows Installer session provided as an argument to a custom action.
+ * \param[in] name
+ * The name of the custom action, used for logging.
+ *
+ * **noexcept** declaration to be added for C++11.
+ */
+ Immediate_Session( MSIHANDLE handle, std::string name ) ;
private:
/*
- * Allow helper function for Installation_Database constructor to have access to the handle.
- */
- friend MSIHANDLE get_active_database( Immediate_Session & session ) ;
+ * Allow helper function for Installation_Database constructor to have access to the handle.
+ */
+ friend msi_handle get_active_database( Immediate_Session & session ) ;
};
@@ -116,29 +200,29 @@
// Deferred_Session
//-----------------------------------------------------------------------------------------
/**
- * Session for deferred custom actions.
- *
- * There's much less context information easily available from a deferred custom action.
- *
- * /sa MDSN "Deferred Execution Custom Actions"
- * http://msdn.microsoft.com/en-us/library/windows/desktop/aa368268%28v=vs.85%29.aspx
- * for general information.
- *
- * /sa MSDN "Obtaining Context Information for Deferred Execution Custom Actions"
- * http://msdn.microsoft.com/en-us/library/windows/desktop/aa370543%28v=vs.85%29.aspx
- * lists the API calls available.
- */
+* Session for deferred custom actions.
+*
+* There's much less context information easily available from a deferred custom action.
+*
+* /sa MDSN "Deferred Execution Custom Actions"
+* http://msdn.microsoft.com/en-us/library/windows/desktop/aa368268%28v=vs.85%29.aspx
+* for general information.
+*
+* /sa MSDN "Obtaining Context Information for Deferred Execution Custom Actions"
+* http://msdn.microsoft.com/en-us/library/windows/desktop/aa370543%28v=vs.85%29.aspx
+* lists the API calls available.
+*/
class Deferred_Session : public Session
{
public:
/**
- * Ordinary constructor.
- *
- * \param[in] handle
- * Handle for the Windows Installer session provided as an argument to a custom action.
- * \param[in] name
- * The name of the custom action, used for logging.
- */
+ * Ordinary constructor.
+ *
+ * \param[in] handle
+ * Handle for the Windows Installer session provided as an argument to a custom action.
+ * \param[in] name
+ * The name of the custom action, used for logging.
+ */
Deferred_Session( MSIHANDLE handle, std::wstring name ) ;
};
@@ -147,10 +231,10 @@
// Commit_Session
//-----------------------------------------------------------------------------------------
/**
- * The session for a commit custom action. NOT IMPLEMENTED.
- *
- * \sa MSDN "Commit Custom Actions" http://msdn.microsoft.com/en-us/library/windows/desktop/aa367991%28v=vs.85%29.aspx
- */
+* The session for a commit custom action. NOT IMPLEMENTED.
+*
+* \sa MSDN "Commit Custom Actions" http://msdn.microsoft.com/en-us/library/windows/desktop/aa367991%28v=vs.85%29.aspx
+*/
class Commit_Session
{
};
@@ -159,10 +243,10 @@
// Rollback_Session
//-----------------------------------------------------------------------------------------
/**
- * The session for a rollback custom action. NOT IMPLEMENTED.
- *
- * \sa MSDN "Rollback Custom Actions" http://msdn.microsoft.com/en-us/library/windows/desktop/aa371369%28v=vs.85%29.aspx
- */
+* The session for a rollback custom action. NOT IMPLEMENTED.
+*
+* \sa MSDN "Rollback Custom Actions" http://msdn.microsoft.com/en-us/library/windows/desktop/aa371369%28v=vs.85%29.aspx
+*/
class Rollback_Session
{
};
Index: installer/src/installer-lib/test/custom-action-fail.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/custom-action-fail.cpp
@@ -0,0 +1,29 @@
+/**
+ * \file close_application.cpp
+ */
+
+#include "session.h"
+
+//-------------------------------------------------------
+// Fail
+//-------------------------------------------------------
+/**
+ * A custom action that always and immediately fails.
+ * Use during testing to ensure that the installer terminates.
+ *
+ * \param[in] session_handle
+ * Windows installer session handle
+ *
+ * \return
+ * An integer interpreted as a custom action return value.
+ *
+ * \sa
+ * - MSDN [Custom Action Return Values](http://msdn.microsoft.com/en-us/library/aa368072%28v=vs.85%29.aspx)
+ */
+extern "C" UINT __stdcall
+fail( MSIHANDLE session_handle )
+{
+ // Instantiate the session object in order to get begin/end log entries.
+ Immediate_Session session( session_handle, "fail" ) ;
+ return ERROR_INSTALL_FAILURE ;
+}
Index: installer/src/installer-lib/test/database_test.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/database_test.cpp
@@ -0,0 +1,66 @@
+#include
+#include "../database.h"
+
+/*
+ * Note: These tests need to be run in the same directory as the MSI file.
+ * There's a batch file for that purpose "run-tests.cmd".
+ */
+
+TEST( Database, open )
+{
+ File_System_Database db( L"test-installer-lib.msi" ) ;
+}
+
+class Database_F
+ : public ::testing::Test
+{
+protected:
+ File_System_Database db ;
+
+ Database_F()
+ : db( L"test-installer-lib.msi" )
+ {}
+} ;
+
+TEST_F( Database_F, view_n_columns_and_n_rows )
+{
+ View v( db, L"SELECT * FROM AbpUIText" ) ;
+ Record r( v.first() ) ;
+ unsigned int j ;
+ for ( j = 0 ; r != v.end() ; ++ j )
+ {
+ ASSERT_EQ( 3, r.n_fields() ) ;
+ r = v.next() ;
+ }
+ ASSERT_EQ( 4, j ) ;
+}
+
+TEST_F( Database_F, view_single_record )
+{
+ View v( db, L"SELECT `content` FROM `AbpUIText` WHERE `component`='close_ie' and `id`='dialog_unknown'" ) ;
+ Record r( v.first() ) ;
+ ASSERT_EQ( 1, r.n_fields() ) ;
+ std::wstring s( r.value_string( 1 ) ) ;
+ std::wstring expected( L"IE is still running" ) ;
+ ASSERT_GT( s.length(), expected.length() ) ;
+ std::wstring prefix( s.substr( 0, expected.length() ) ) ;
+ ASSERT_EQ( prefix, expected ) ;
+ r = v.next() ;
+ ASSERT_EQ( v.end(), r ) ;
+}
+
+TEST_F( Database_F, view_single_record_parametric )
+{
+ View v( db, L"SELECT `content` FROM `AbpUIText` WHERE `component`='close_ie' and `id`=?" ) ;
+ Record arg( 1 ) ;
+ arg.assign_string( 1, L"dialog_unknown" ) ;
+ Record r( v.first( arg ) ) ;
+ ASSERT_EQ( 1, r.n_fields() ) ;
+ std::wstring s( r.value_string( 1 ) ) ;
+ std::wstring expected( L"IE is still running" ) ;
+ ASSERT_GT( s.length(), expected.length() ) ;
+ std::wstring prefix( s.substr( 0, expected.length() ) ) ;
+ ASSERT_EQ( prefix, expected ) ;
+ r = v.next() ;
+ ASSERT_EQ( v.end(), r ) ;
+}
Index: installer/src/installer-lib/test/exception_test.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/exception_test.cpp
@@ -0,0 +1,58 @@
+/**
+ * \file exception_test.cpp Unit tests for the library-wide exception classes in installer-lib.h
+ */
+
+#include
+
+#include
+
+#include "../installer-lib.h"
+
+TEST( Exception_Test, empty_two )
+{
+ ::SetLastError( 0 ) ;
+ windows_api_error e( "", "" ) ;
+ ASSERT_STREQ( " returned with last error code 0", e.what() ) ;
+}
+
+TEST( Exception_Test, empty_three )
+{
+ ::SetLastError( 1 ) ;
+ windows_api_error e( "", "", "" ) ;
+ ASSERT_STREQ( " returned with last error code 1", e.what() ) ;
+}
+
+TEST( Exception_Test, empty_empty_message )
+{
+ ::SetLastError( 2 ) ;
+ windows_api_error e( "", "", "message" ) ;
+ ASSERT_STREQ( " returned with last error code 2: message", e.what() ) ;
+}
+
+TEST( Exception_Test, string_number )
+{
+ ::SetLastError( 3 ) ;
+ windows_api_error e( "Beep", 1 ) ;
+ ASSERT_STREQ( "Beep returned 1 with last error code 3", e.what() ) ;
+}
+
+TEST( Exception_Test, string_number_message )
+{
+ ::SetLastError( 4 ) ;
+ windows_api_error e( "Beep", 1, "message" ) ;
+ ASSERT_STREQ( "Beep returned 1 with last error code 4: message", e.what() ) ;
+}
+
+TEST( Exception_Test, string_string )
+{
+ ::SetLastError( 5 ) ;
+ windows_api_error e( "GetErrorMode", "SEM_FAILCRITICALERRORS" ) ;
+ ASSERT_STREQ( "GetErrorMode returned SEM_FAILCRITICALERRORS with last error code 5", e.what() ) ;
+}
+
+TEST( Exception_Test, string_string_message )
+{
+ ::SetLastError( 6 ) ;
+ windows_api_error e( "GetErrorMode", "SEM_FAILCRITICALERRORS", "message" ) ;
+ ASSERT_STREQ( "GetErrorMode returned SEM_FAILCRITICALERRORS with last error code 6: message", e.what() ) ;
+}
Index: installer/src/installer-lib/test/process_test.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/process_test.cpp
@@ -0,0 +1,316 @@
+#include
+#include "../process.h"
+#include
+
+// Turn off warnings for string copies
+#pragma warning( disable : 4996 )
+
+//-------------------------------------------------------
+// Comparison objects
+//-------------------------------------------------------
+
+const wchar_t * multiple_module_names[] = { L"kernel32.dll", L"non-matching-name" } ;
+const wchar_t * non_existent_module_names[] = { L"non-matching-name" } ;
+
+const wchar_t exact_exe_name[] = L"installer-ca-tests.exe" ;
+const std::wstring exact_exe_string( exact_exe_name ) ;
+const wstring_ci exact_exe_string_ci( exact_exe_name ) ;
+
+const wchar_t mixedcase_exe_name[] = L"Installer-CA-Tests.exe" ;
+const wstring_ci mixedcase_exe_string_ci( mixedcase_exe_name ) ;
+
+const wchar_t unknown_name[] = L"non-matching-name" ;
+const wchar_t * multiple_exe_names[] = { mixedcase_exe_name, unknown_name } ;
+
+/**
+ * Compare to our own process name, case-sensitive, no length limit
+ */
+struct our_process_by_name
+ : std::unary_function< PROCESSENTRY32W, bool >
+{
+ bool operator()( const PROCESSENTRY32W & process )
+ {
+ return std::wstring( process.szExeFile ) == exact_exe_string ;
+ } ;
+};
+
+/**
+ * Compare to our own process name, case-insensitive, no length limit
+ */
+struct our_process_by_name_CI
+ : std::unary_function< PROCESSENTRY32W, bool >
+{
+ bool operator()( const PROCESSENTRY32W & process )
+ {
+ return wstring_ci( process.szExeFile ) == mixedcase_exe_string_ci ;
+ } ;
+} ;
+
+
+struct our_process_by_name_subclassed
+ : public process_by_any_exe_with_any_module
+{
+ our_process_by_name_subclassed()
+ : process_by_any_exe_with_any_module( file_name_set( multiple_exe_names ), file_name_set() )
+ {}
+} ;
+
+
+//-------------------------------------------------------
+//-------------------------------------------------------
+/**
+ * Filter by process name. Comparison is case-insensitive.
+ */
+class process_by_any_file_name_CI
+ : public std::unary_function< PROCESSENTRY32W, bool >
+{
+ const file_name_set & names ;
+public:
+ bool operator()( const PROCESSENTRY32W & process)
+ {
+ return names.find( process.szExeFile ) != names.end() ;
+ }
+ process_by_any_file_name_CI( const file_name_set & names )
+ : names( names )
+ {}
+} ;
+
+/**
+ * Filter by process name. Comparison is case-insensitive.
+ */
+class process_by_name_CI
+ : public std::unary_function< PROCESSENTRY32W, bool >
+{
+ const wstring_ci _name ;
+public:
+ bool operator()( const PROCESSENTRY32W & process )
+ {
+ return _name == wstring_ci( process.szExeFile ) ;
+ }
+
+ process_by_name_CI( const wchar_t * name )
+ : _name( name )
+ {}
+} ;
+
+//-------------------------------------------------------
+// TESTS, no snapshots
+//-------------------------------------------------------
+PROCESSENTRY32 process_with_name( const wchar_t * s )
+{
+ PROCESSENTRY32W p ;
+ wcsncpy( p.szExeFile, s, MAX_PATH ) ;
+ return p ;
+}
+
+PROCESSENTRY32 process_empty = process_with_name( L"" ) ;
+PROCESSENTRY32 process_exact = process_with_name( exact_exe_name ) ;
+PROCESSENTRY32 process_mixedcase = process_with_name( mixedcase_exe_name ) ;
+PROCESSENTRY32 process_explorer = process_with_name( L"explorer.exe" ) ;
+PROCESSENTRY32 process_absent = process_with_name( L"no_such_name" ) ;
+
+file_name_set multiple_name_set( multiple_exe_names ) ;
+file_name_set multiple_name_set_modules( multiple_module_names ) ;
+file_name_set non_existent_name_set_modules( non_existent_module_names ) ;
+process_by_any_file_name_CI find_in_set( multiple_name_set ) ;
+process_by_any_exe_with_any_module find_in_set_w_kernel32( multiple_name_set, multiple_name_set_modules ) ;
+process_by_any_exe_with_any_module find_in_set_w_non_existent( multiple_name_set, non_existent_name_set_modules ) ;
+
+TEST( file_name_set, validate_setup )
+{
+ ASSERT_EQ( 2u, multiple_name_set.size() ) ;
+ ASSERT_TRUE( multiple_name_set.find( exact_exe_string_ci ) != multiple_name_set.end() ) ;
+ ASSERT_TRUE( multiple_name_set.find( mixedcase_exe_string_ci ) != multiple_name_set.end() ) ;
+ ASSERT_TRUE( multiple_name_set.find( L"" ) == multiple_name_set.end() ) ;
+ ASSERT_TRUE( multiple_name_set.find( L"not-in-list" ) == multiple_name_set.end() ) ;
+}
+
+TEST( process_by_any_file_name_CI, empty )
+{
+ file_name_set s ;
+ process_by_any_file_name_CI x( s ) ;
+
+ ASSERT_FALSE( x( process_empty ) ) ;
+ ASSERT_FALSE( x( process_exact ) ) ;
+ ASSERT_FALSE( x( process_mixedcase ) ) ;
+ ASSERT_FALSE( x( process_explorer ) ) ;
+ ASSERT_FALSE( x( process_absent ) ) ;
+}
+
+TEST( process_by_any_file_name_CI, single_element_known )
+{
+ const wchar_t * elements[ 1 ] = { exact_exe_name } ;
+ file_name_set s( elements ) ;
+ process_by_any_file_name_CI x( s ) ;
+
+ ASSERT_FALSE( x( process_empty ) ) ;
+ ASSERT_TRUE( x( process_exact ) ) ;
+ ASSERT_TRUE( x( process_mixedcase ) ) ;
+ ASSERT_FALSE( x( process_explorer ) ) ;
+ ASSERT_FALSE( x( process_absent ) ) ;
+}
+
+TEST( process_by_any_file_name_CI, single_element_unknown )
+{
+ const wchar_t * elements[ 1 ] = { unknown_name } ;
+ file_name_set s( elements ) ;
+ process_by_any_file_name_CI x( s ) ;
+
+ ASSERT_FALSE( x( process_empty ) ) ;
+ ASSERT_FALSE( x( process_exact ) ) ;
+ ASSERT_FALSE( x( process_mixedcase ) ) ;
+ ASSERT_FALSE( x( process_explorer ) ) ;
+ ASSERT_FALSE( x( process_absent ) ) ;
+}
+
+TEST( process_by_any_file_name_CI, two_elements )
+{
+ file_name_set s( multiple_exe_names ) ;
+ process_by_any_file_name_CI x( s ) ;
+
+ ASSERT_FALSE( find_in_set( process_empty ) ) ;
+ ASSERT_TRUE( find_in_set( process_exact ) ) ;
+ ASSERT_TRUE( find_in_set( process_mixedcase ) ) ;
+ ASSERT_FALSE( find_in_set( process_explorer ) ) ;
+ ASSERT_FALSE( find_in_set( process_absent ) ) ;
+}
+
+//-------------------------------------------------------
+// Single-snapshot version of initializers
+//-------------------------------------------------------
+/**
+ * Single-snapshot version of initialize_process_list, for testing.
+ */
+template< class T, class Admittance, class Extractor >
+void initialize_process_list( std::vector< T > & v, Admittance admit = Admittance(), Extractor extract = Extractor() )
+{
+ initialize_process_list( v, Process_Snapshot(), admit, extract ) ;
+}
+
+/**
+ * Single-snapshot version of initialize_process_set, for testing.
+ */
+template< class T, class Admittance, class Extractor >
+void initialize_process_set( std::set< T > & s, Admittance admit = Admittance(), Extractor extract = Extractor() )
+{
+ initialize_process_set( s, Process_Snapshot(), admit, extract ) ;
+}
+
+//-------------------------------------------------------
+// TESTS with snapshots
+//-------------------------------------------------------
+/**
+ * Construction test ensures that we don't throw and that at least one process shows up.
+ */
+TEST( Process_List_Test, construct_vector )
+{
+ std::vector< PROCESSENTRY32W > v ;
+ initialize_process_list( v, every_process(), copy_all() ) ;
+ ASSERT_GE( v.size(), 1u );
+}
+
+/**
+ * The only process we are really guaranteed to have is this test process itself.
+ */
+TEST( Process_List_Test, find_our_process )
+{
+ std::vector< PROCESSENTRY32W > v ;
+ initialize_process_list( v, our_process_by_name(), copy_all() ) ;
+ size_t size( v.size() );
+ EXPECT_EQ( 1u, size ); // Please, don't run multiple test executables simultaneously
+ ASSERT_GE( 1u, size );
+}
+
+/**
+ * The only process we are really guaranteed to have is this test process itself.
+ * This test uses same one used in Process_Closer
+ */
+TEST( Process_List_Test, find_our_process_CI_generic )
+{
+ std::vector< PROCESSENTRY32W > v ;
+ initialize_process_list( v, process_by_name_CI( mixedcase_exe_name ), copy_all() ) ;
+ size_t size( v.size() );
+ EXPECT_EQ( 1u, size ); // Please, don't run multiple test executables simultaneously
+ ASSERT_GE( 1u, size );
+}
+
+/**
+ * The only process we are really guaranteed to have is this test process itself.
+ * This test uses the generic filter function.
+ */
+TEST( Process_List_Test, find_our_process_CI_as_used )
+{
+ std::vector< PROCESSENTRY32W > v ;
+ initialize_process_list( v, process_by_any_file_name_CI( file_name_set( multiple_exe_names ) ), copy_all() ) ;
+ size_t size( v.size() );
+ EXPECT_EQ( 1u, size ); // Please, don't run multiple test executables simultaneously
+ ASSERT_GE( 1u, size );
+}
+
+/**
+ * Locate the PID of our process.
+ */
+TEST( Process_List_Test, find_our_PID )
+{
+ std::vector< DWORD > v ;
+ initialize_process_list( v, our_process_by_name(), copy_PID() ) ;
+ size_t size( v.size() );
+ EXPECT_EQ( size, 1u ); // Please, don't run multiple test executables simultaneously
+ ASSERT_GE( size, 1u );
+}
+
+/**
+ * Locate the PID of our process using the
+ */
+TEST( Process_List_Test, find_our_process_in_set )
+{
+ std::vector< DWORD > v ;
+ initialize_process_list( v, find_in_set, copy_PID() ) ;
+ size_t size( v.size() );
+ EXPECT_EQ( size, 1u ); // Please, don't run multiple test executables simultaneously
+ ASSERT_GE( size, 1u );
+}
+
+//-------------------------------------------------------
+// TESTS for process ID sets
+//-------------------------------------------------------
+/*
+ * Can't use copy_all without a definition for "less< PROCESSENTRY32W >".
+ * Thus all tests only use copy_PID
+ */
+
+/**
+ * Construction test ensures that we don't throw and that at least one process shows up.
+ */
+TEST( pid_set, construct_set )
+{
+ std::set< DWORD > s ;
+ initialize_process_set( s, every_process(), copy_PID() ) ;
+ ASSERT_GE( s.size(), 1u );
+}
+
+TEST( pid_set, find_our_process_in_set )
+{
+ std::set< DWORD > s ;
+ initialize_process_set( s, find_in_set, copy_PID() ) ;
+ size_t size( s.size() ) ;
+ EXPECT_EQ( size, 1u );
+ ASSERT_GE( size, 1u );
+}
+
+TEST( pid_set, find_our_process_in_set_w_kernel32 )
+{
+ std::set< DWORD > s ;
+ initialize_process_set( s, find_in_set_w_kernel32, copy_PID() ) ;
+ size_t size( s.size() ) ;
+ EXPECT_EQ( size, 1u );
+ ASSERT_GE( size, 1u );
+}
+TEST( pid_set, find_our_process_in_set_w_non_existant )
+{
+ std::set< DWORD > s ;
+ initialize_process_set( s, find_in_set_w_non_existent, copy_PID() ) ;
+ size_t size( s.size() ) ;
+ EXPECT_EQ( size, 0u );
+ ASSERT_GE( size, 0u );
+}
Index: installer/src/installer-lib/test/property_test.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/property_test.cpp
@@ -0,0 +1,40 @@
+#include
+
+#include "../database.h"
+#include "../property.h"
+
+TEST( Property_Test, null )
+{
+ /*
+ * This is an extract of manual test code originally run from abp_close_ie DLL entry point.
+ * This code relies on an MSI database opened for installation, which we don't need to access properties.
+ * We can instead use an offline session, with the database opened outside Windows Installer.
+ * That session class, though, isn't written yet.
+ */
+ /*
+ * DISABLED. Refactor into proper tests.
+ */
+ if ( false )
+ {
+ // This variable was the argument to the entry point.
+ MSIHANDLE session_handle = 0;
+
+ // The code in the body.
+ Immediate_Session session( session_handle, "abp_close_ie" ) ;
+ session.log( L"Have session object" ) ;
+ Installation_Database db( session ) ;
+ session.log( L"Have database object" ) ;
+
+ // Test: ensure that a property is present with its expected value. Exercises the conversion operator to String.
+ session.log( L"VersionMsi = " + Property( session, L"VersionMsi" ) ) ;
+
+ // Test: create a property dynamically from within the CA. Not sure if this can be done offline.
+ Property tv( session, L"TESTVARIABLE" ) ;
+ session.log( L"TESTVARIABLE = " + tv ) ;
+
+ // Test: assign a new value to a property.
+ session.log( L"Setting TESTVARIABLE to 'testvalue'" ) ;
+ tv = L"testvalue" ;
+ session.log( L"TESTVARIABLE = " + tv ) ;
+ }
+}
\ No newline at end of file
Index: installer/src/installer-lib/test/record_test.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/record_test.cpp
@@ -0,0 +1,8 @@
+#include
+
+#include "../record.h"
+
+TEST( Record_Test, construct )
+{
+ Record r(1);
+}
\ No newline at end of file
Index: installer/src/installer-lib/test/test-installer-lib-ca.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/test-installer-lib-ca.cpp
@@ -0,0 +1,71 @@
+/**
+ * \file abp_ca.cpp Top-level source for custom actions. Includes DLL initialization.
+ */
+#include "DLL.h"
+#include
+
+/**
+ * DllMain is the standard entry point call when the DLL is loaded or unloaded.
+ *
+ * \param[in] module_handle
+ * Handle for this instance of the DLL; same as the module handle.
+ * This handle allows us to get the DLL file name for logging.
+ * \param[in] reason
+ * The point in the DLL life cycle at which this call is made. Called "reason code" by Microsoft.
+ * \param[in] reserved
+ * No longer reserved, since it contains a point in the thread life cycle.
+ * We aren't using it, though.
+
+ * \sa { http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583%28v=vs.85%29.aspx }
+ * Documentation on DLL entry points in Windows.
+ */
+extern "C" BOOL WINAPI DllMain(
+ IN HINSTANCE module_handle,
+ IN ULONG reason,
+ IN LPVOID reserved )
+{
+ /*
+ * Because this is an external API, we must ensure that there is a catch-all block for each execution path. There are two of these below.
+ */
+ switch ( reason )
+ {
+ case DLL_PROCESS_ATTACH:
+ try
+ {
+ DLL_Module::attach( module_handle );
+ return TRUE;
+ }
+ catch(...)
+ {
+ // We can't log to the installation log yet, and this couldn't shouldn't be executed except in rare cases such as out-of-memory.
+ // Since it's a lot of code to do something useful (such as logging to the Windows system event log), we don't do anything but return a failure.
+ return FALSE;
+ }
+ break;
+
+ case DLL_PROCESS_DETACH:
+ try
+ {
+ DLL_Module::detach();
+ return TRUE;
+ }
+ catch(...)
+ {
+ // See comment above in parallel catch-block.
+ return FALSE;
+ }
+ break;
+
+ /*
+ * This entry point is called for each thread after the first in a process with this DLL loaded. Note "after the first".
+ * The process life cycle is always called, and we do our global initialization there. So even though this DLL
+ * doesn't support asynchronous operation, this entry point gets called anyway. We need to ignore these calls.
+ */
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
Index: installer/src/installer-lib/test/test-installer-lib-ca.def
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/test-installer-lib-ca.def
@@ -0,0 +1,13 @@
+; -------------------------------------------------------------------------------------------------
+; copyright notice goes here
+; -------------------------------------------------------------------------------------------------
+
+LIBRARY "installer-library-test-customactions"
+
+EXPORTS
+; test-installer-lib-sandbox.cpp
+ sandbox
+; close_application.cpp
+ abp_close_ie
+; custom-action-fail.cpp
+ fail
Index: installer/src/installer-lib/test/test-installer-lib-ca.rc
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/test-installer-lib-ca.rc
@@ -0,0 +1,26 @@
+// Resource file for the custom action library
+
+#include
+
+VS_VERSION_INFO VERSIONINFO
+FILEVERSION 0.0.1
+PRODUCTVERSION 0.0.1
+FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+FILEFLAGS ( VS_FF_DEBUG | VS_FF_PRERELEASE )
+FILEOS VOS__WINDOWS32
+FILETYPE VFT_DLL
+FILESUBTYPE 0
+BEGIN
+BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "000004E4"
+ BEGIN
+ VALUE "CompanyName", "Eyeo, GmbH"
+ VALUE "FileDescription", "Custom action DLL for testing the installer library"
+ VALUE "FileVersion", "0.0.1"
+ VALUE "InternalName", "installer-library-test-customactions"
+ VALUE "OriginalFilename", "installer-library-test-customactions.dll"
+ VALUE "ProductName", "ABP installer library test CA"
+ END
+ END
+END
\ No newline at end of file
Index: installer/src/installer-lib/test/test-installer-lib-sandbox.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/test-installer-lib-sandbox.cpp
@@ -0,0 +1,142 @@
+/**
+ * \file test-installer-lib-sandbox.cpp
+ *
+ * Automatic testing of many of the units within the custom action is infeasible.
+ * In one case, they rely on the execution environment within an installation session.
+ * In another, they rely on the operation system environment as a whole.
+ * In these cases, it's easier to verify behavior manually.
+ *
+ * This file contains a custom action function sandbox() as well as a number of test functions.
+ * At any given time, not all of the test functions need to be referenced within the body of custom action.
+ */
+
+#include
+#include
+
+#include "session.h"
+#include "property.h"
+#include "database.h"
+#include "process.h"
+#include "interaction.h"
+
+//-------------------------------------------------------
+// log_all_window_handles
+//-------------------------------------------------------
+class log_single_window_handle
+{
+ Immediate_Session & session ;
+
+public:
+ log_single_window_handle( Immediate_Session & session )
+ : session( session )
+ {
+ }
+
+ bool operator()( HWND window )
+ {
+ std::stringstream s ;
+ s << "Window handle 0x" << std::hex << window ;
+ session.log( s.str() ) ;
+ return true ;
+ }
+} ;
+
+void log_all_window_handles( Immediate_Session & session )
+{
+ session.log( "log_all_window_handles" ) ;
+ log_single_window_handle lp( session ) ;
+ enumerate_windows( lp ) ;
+}
+
+//-------------------------------------------------------
+// log_IE_window_handles
+//-------------------------------------------------------
+class log_single_window_handle_only_if_IE
+{
+ Immediate_Session & session ;
+
+ Process_Closer & pc ;
+
+public:
+ log_single_window_handle_only_if_IE( Immediate_Session & session, Process_Closer & pc )
+ : session( session ), pc( pc )
+ {
+ }
+
+ bool operator()( HWND window )
+ {
+ DWORD pid = creator_process( window ) ;
+ if ( pc.contains( pid ) )
+ {
+ std::stringstream s ;
+ s << "Window handle 0x" << std::hex << window ;
+ session.log( s.str() ) ;
+ }
+ return true ;
+ }
+} ;
+
+void log_IE_window_handles( Immediate_Session & session )
+{
+ session.log( "log_IE_window_handles" ) ;
+ const wchar_t * IE_names[] = { L"IExplore.exe", L"AdblockPlusEngine.exe" } ;
+ const wchar_t * ABP_names[] = { L"AdblockPlus32.dll", L"AdblockPlus64.dll" } ;
+ Process_Snapshot snapshot ;
+ Process_Closer iec( snapshot, IE_names, ABP_names) ;
+ log_single_window_handle_only_if_IE lp( session, iec ) ;
+ enumerate_windows( lp ) ;
+}
+
+//-------------------------------------------------------
+// log_only_window_handle_in_closer
+//-------------------------------------------------------
+void log_only_window_handle_in_closer( Immediate_Session & session )
+{
+ session.log( "log_only_window_handle_in_closer" ) ;
+ const wchar_t * IE_names[] = { L"IExplore.exe", L"AdblockPlusEngine.exe" } ;
+ const wchar_t * ABP_names[] = { L"AdblockPlus32.dll", L"AdblockPlus64.dll" } ;
+ Process_Snapshot snapshot ;
+ Process_Closer iec( snapshot, IE_names, ABP_names) ;
+ iec.iterate_our_windows( log_single_window_handle( session ) ) ;
+}
+
+//-------------------------------------------------------
+// sandbox
+//-------------------------------------------------------
+/**
+ * Exposed DLL entry point for custom action.
+ * The function signature matches the calling convention used by Windows Installer.
+
+ * \param[in] session_handle
+ * Windows installer session handle
+ *
+ * \return
+ * An integer interpreted as a custom action return value.
+ *
+ * \sa
+ * - MSDN [Custom Action Return Values](http://msdn.microsoft.com/en-us/library/aa368072%28v=vs.85%29.aspx)
+ */
+extern "C" UINT __stdcall
+sandbox( MSIHANDLE session_handle )
+{
+ Immediate_Session session( session_handle, "sandbox" ) ;
+
+ try
+ {
+ session.log( "Sandbox timestamp " __TIMESTAMP__ ) ;
+ log_only_window_handle_in_closer( session ) ;
+ }
+ catch( std::exception & e )
+ {
+ session.log_noexcept( "terminated by exception: " + std::string( e.what() ) ) ;
+ return ERROR_INSTALL_FAILURE ;
+ }
+ catch( ... )
+ {
+ session.log_noexcept( "Caught an exception" ) ;
+ return ERROR_INSTALL_FAILURE ;
+ }
+
+ return ERROR_SUCCESS ;
+}
+
Index: installer/src/installer-lib/test/test-installer-lib.wxs
===================================================================
new file mode 100644
--- /dev/null
+++ b/installer/src/installer-lib/test/test-installer-lib.wxs
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: installer/src/msi/adblockplusie.wxs
===================================================================
rename from installer/adblockplusie.wxs
rename to installer/src/msi/adblockplusie.wxs
--- a/installer/adblockplusie.wxs
+++ b/installer/src/msi/adblockplusie.wxs
@@ -8,7 +8,9 @@
Certain WiX elements are implemented by MSI custom actions,
which means this source implicitly declares some insofar as the Windows Installer sees it.
These errors are benign and can be avoided by some fiddling with the registry on the development machine.
- See http://wix.sourceforge.net/faq.html#Error217
+ See http://blogs.msdn.com/b/heaths/archive/2007/05/31/windows-installer-errors-2738-and-2739-with-script-custom-actions.aspx
+ and http://blogs.msdn.com/b/astebner/archive/2007/06/07/3151752.aspx,
+ both taken from (must use the Wayback Machine) http://wix.sourceforge.net/faq.html#Error217
-->
+
+
+
+
+
@@ -37,15 +50,29 @@
+
+
+
+
+
+
+
+
+
+
@@ -69,7 +122,6 @@
implied and allows major upgrades to be performed. Downgrades aren't
allowed by default.
-
The @Schedule attribute looks a little opaque.
Suffice it to say that it's the only option for scheduling where uninstallation of the previous version
and installation of the present version is _always_ going to act atomically.
@@ -94,8 +146,7 @@
The "CustomAction" tag defines the tag itself, but doesn't sequence it;
it defines an entry in the "CustomAction" table.
The "Binary" tag describe an asset that's incorporated into the MSI but that is not an installed component.
- The "Custom" tag defines the sequencing of an action;
- it defines an entry in one of the sequence tables (there are six).
+ The "Custom" tag defines when the action executes by defining an entry in one of the sequence tables.
Attribute "Return" sets the "Custom Action Return Processing Options" to zero,
which indicates to block the installer until the action completes
@@ -104,10 +155,14 @@
-
+
+
-
+
@@ -265,7 +320,7 @@
-->
-
+
@@ -277,11 +332,11 @@
-->
-
+
-
+
@@ -322,7 +377,7 @@
-
+
@@ -510,7 +565,7 @@
-
+
@@ -765,11 +820,14 @@
-
+
+
+
+
Index: installer/src/msi/bho_registry_value.wxi
===================================================================
rename from installer/bho_registry_value.wxi
rename to installer/src/msi/bho_registry_value.wxi
Index: installer/src/msi/custom_WixUI_InstallDir.wxs
===================================================================
rename from installer/custom_WixUI_InstallDir.wxs
rename to installer/src/msi/custom_WixUI_InstallDir.wxs
Index: installer/src/msi/dll_class.wxi
===================================================================
rename from installer/dll_class.wxi
rename to installer/src/msi/dll_class.wxi
Index: installer/src/setup-exe/abp-64.png
===================================================================
rename from installer/abp-64.png
rename to installer/src/setup-exe/abp-64.png
Index: installer/src/setup-exe/bootstrap-theme.wxl
===================================================================
rename from installer/bootstrap-theme.wxl
rename to installer/src/setup-exe/bootstrap-theme.wxl
Index: installer/src/setup-exe/bootstrap-theme.xml
===================================================================
rename from installer/bootstrap-theme.xml
rename to installer/src/setup-exe/bootstrap-theme.xml
Index: installer/src/setup-exe/setup.wxs
===================================================================
rename from installer/setup.wxs
rename to installer/src/setup-exe/setup.wxs
--- a/installer/setup.wxs
+++ b/installer/src/setup-exe/setup.wxs
@@ -7,6 +7,9 @@
+
+
+
-
+
-
-
-
-
-
+
Index: src/engine/Main.cpp
===================================================================
--- a/src/engine/Main.cpp
+++ b/src/engine/Main.cpp
@@ -396,7 +396,6 @@
int argc;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
std::wstring locale(argc >= 2 ? argv[1] : L"");
- Communication::browserSID = argc >= 3 ? argv[2] : L"";
LocalFree(argv);
Dictionary::Create(locale);
filterEngine = CreateFilterEngine(locale);
Index: src/plugin/AdblockPlusClient.cpp
===================================================================
--- a/src/plugin/AdblockPlusClient.cpp
+++ b/src/plugin/AdblockPlusClient.cpp
@@ -44,22 +44,31 @@
// Running inside AppContainer?
if (acs != NULL && acs->TokenAppContainer != NULL)
{
- // Launch with default security. Registry entry will eat the user prompt
+ // We need to break out from AppContainer. Launch with default security - registry entry will eat the user prompt
// See http://msdn.microsoft.com/en-us/library/bb250462(v=vs.85).aspx#wpm_elebp
- LPWSTR stringSid;
- ConvertSidToStringSidW(acs->TokenAppContainer, &stringSid);
- params.Append(L" ");
- params.Append(stringSid);
- LocalFree(stringSid);
createProcRes = CreateProcessW(engineExecutablePath.c_str(), params.GetBuffer(params.GetLength() + 1),
0, 0, false, 0, 0, 0, (STARTUPINFOW*)&startupInfo, &processInformation);
}
else
{
- // Launch with the same security token (Low Integrity) explicitly
+ // Launch with Low Integrity explicitly
HANDLE newToken;
DuplicateTokenEx(token, 0, 0, SecurityImpersonation, TokenPrimary, &newToken);
+ PSID integritySid = 0;
+ ConvertStringSidToSid(L"S-1-16-4096", &integritySid);
+ std::tr1::shared_ptr sharedIntegritySid(static_cast(integritySid), FreeSid); // Just to simplify cleanup
+
+ TOKEN_MANDATORY_LABEL tml = {};
+ tml.Label.Attributes = SE_GROUP_INTEGRITY;
+ tml.Label.Sid = integritySid;
+
+ // Set the process integrity level
+ SetTokenInformation(newToken, TokenIntegrityLevel, &tml, sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(integritySid));
+
+ STARTUPINFO startupInfo = {};
+ PROCESS_INFORMATION processInformation = {};
+
createProcRes = CreateProcessAsUserW(newToken, engineExecutablePath.c_str(), params.GetBuffer(params.GetLength() + 1),
0, 0, false, 0, 0, 0, (STARTUPINFOW*)&startupInfo, &processInformation);
}
Index: src/shared/Communication.cpp
===================================================================
--- a/src/shared/Communication.cpp
+++ b/src/shared/Communication.cpp
@@ -8,11 +8,7 @@
#include "Communication.h"
#include "Utils.h"
-#ifndef SECURITY_APP_PACKAGE_AUTHORITY
-#define SECURITY_APP_PACKAGE_AUTHORITY {0,0,0,0,0,15}
-#endif
-std::wstring Communication::browserSID;
namespace
{
@@ -56,12 +52,11 @@
return sid;
}
- std::auto_ptr CreateObjectSecurityDescriptor(PSID logonSid)
+ // Creates a security descriptor:
+ // Allows ALL access to Logon SID and to all app containers in DACL.
+ // Sets Low Integrity in SACL.
+ std::auto_ptr CreateSecurityDescriptor(PSID logonSid)
{
- PSID browserSid = 0;
- std::tr1::shared_ptr sharedBrowserSid(reinterpret_cast(browserSid), FreeSid); // Just to simplify cleanup
- ConvertStringSidToSid(Communication::browserSID.c_str(), &browserSid);
-
EXPLICIT_ACCESSW explicitAccess[2] = {};
explicitAccess[0].grfAccessPermissions = STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL;
@@ -71,25 +66,65 @@
explicitAccess[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
explicitAccess[0].Trustee.ptstrName = static_cast(logonSid);
- explicitAccess[1].grfAccessPermissions = STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL;
- explicitAccess[1].grfAccessMode = SET_ACCESS;
- explicitAccess[1].grfInheritance= NO_INHERITANCE;
- explicitAccess[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
- explicitAccess[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
- explicitAccess[1].Trustee.ptstrName = static_cast(browserSid);
+ std::tr1::shared_ptr sharedAllAppContainersSid;
+ // TODO: Would be better to detect if AppContainers are supported instead of checking the Windows version
+ bool isAppContainersSupported = IsAppContainersSupported();
+ if (isAppContainersSupported)
+ {
+ // Create a well-known SID for the all appcontainers group.
+ // We need to allow access to all AppContainers, since, apparently,
+ // giving access to specific AppContainer (for example AppContainer of IE)
+ // tricks Windows into thinking that token is IN AppContainer.
+ // Which blocks all the calls from outside, making it impossible to communicate
+ // with the engine when IE is launched with different security settings.
+ PSID allAppContainersSid = 0;
+ SID_IDENTIFIER_AUTHORITY applicationAuthority = SECURITY_APP_PACKAGE_AUTHORITY;
+ AllocateAndInitializeSid(&applicationAuthority,
+ SECURITY_BUILTIN_APP_PACKAGE_RID_COUNT,
+ SECURITY_APP_PACKAGE_BASE_RID,
+ SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE,
+ 0, 0, 0, 0, 0, 0,
+ &allAppContainersSid);
+ sharedAllAppContainersSid.reset(static_cast(allAppContainersSid), FreeSid); // Just to simplify cleanup
+
+ explicitAccess[1].grfAccessPermissions = STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL;
+ explicitAccess[1].grfAccessMode = SET_ACCESS;
+ explicitAccess[1].grfInheritance= NO_INHERITANCE;
+ explicitAccess[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ explicitAccess[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
+ explicitAccess[1].Trustee.ptstrName = static_cast(allAppContainersSid);
+ }
PACL acl = 0;
- std::tr1::shared_ptr sharedAcl(acl, FreeSid); // Just to simplify cleanup
- if (SetEntriesInAcl(2, explicitAccess, 0, &acl) != ERROR_SUCCESS)
+ if (SetEntriesInAcl(isAppContainersSupported ? 2 : 1, explicitAccess, 0, &acl) != ERROR_SUCCESS)
return std::auto_ptr(0);
+ std::tr1::shared_ptr sharedAcl(static_cast(acl), LocalFree); // Just to simplify cleanup
- std::auto_ptr securityDescriptor(new SECURITY_DESCRIPTOR[SECURITY_DESCRIPTOR_MIN_LENGTH]);
+ std::auto_ptr securityDescriptor((SECURITY_DESCRIPTOR*)new char[SECURITY_DESCRIPTOR_MIN_LENGTH]);
if (!InitializeSecurityDescriptor(securityDescriptor.get(), SECURITY_DESCRIPTOR_REVISION))
return std::auto_ptr(0);
if (!SetSecurityDescriptorDacl(securityDescriptor.get(), TRUE, acl, FALSE))
return std::auto_ptr(0);
+ // Create a dummy security descriptor with low integrirty preset and copy its SACL into ours
+ LPCWSTR accessControlEntry = L"S:(ML;;NW;;;LW)";
+ PSECURITY_DESCRIPTOR dummySecurityDescriptorLow;
+ ConvertStringSecurityDescriptorToSecurityDescriptorW(accessControlEntry, SDDL_REVISION_1, &dummySecurityDescriptorLow, 0);
+ std::tr1::shared_ptr sharedDummySecurityDescriptor(static_cast(dummySecurityDescriptorLow), LocalFree); // Just to simplify cleanup
+ BOOL saclPresent = FALSE;
+ BOOL saclDefaulted = FALSE;
+ PACL sacl;
+ GetSecurityDescriptorSacl(dummySecurityDescriptorLow, &saclPresent, &sacl, &saclDefaulted);
+ if (saclPresent)
+ {
+ if (!SetSecurityDescriptorSacl(securityDescriptor.get(), TRUE, sacl, FALSE))
+ {
+ DWORD err = GetLastError();
+ return std::auto_ptr(0);
+ }
+ }
+
return securityDescriptor;
}
}
@@ -146,27 +181,18 @@
std::tr1::shared_ptr sharedSecurityDescriptor; // Just to simplify cleanup
- const bool inAppContainer = !browserSID.empty();
- if (inAppContainer)
- {
- AutoHandle token;
- OpenProcessToken(GetCurrentProcess(), TOKEN_READ, token);
- std::auto_ptr logonSid = GetLogonSid(token);
- std::auto_ptr securityDescriptor = CreateObjectSecurityDescriptor(logonSid.get());
- securityAttributes.lpSecurityDescriptor = securityDescriptor.release();
- sharedSecurityDescriptor.reset(static_cast(securityAttributes.lpSecurityDescriptor));
- }
- else if (IsWindowsVistaOrLater())
- {
- // Low mandatory label. See http://msdn.microsoft.com/en-us/library/bb625958.aspx
- LPCWSTR accessControlEntry = L"S:(ML;;NW;;;LW)";
- ConvertStringSecurityDescriptorToSecurityDescriptorW(accessControlEntry, SDDL_REVISION_1,
- &securityAttributes.lpSecurityDescriptor, 0);
- sharedSecurityDescriptor.reset(static_cast(securityAttributes.lpSecurityDescriptor), LocalFree);
- }
+ AutoHandle token;
+ OpenProcessToken(GetCurrentProcess(), TOKEN_READ, token);
+ std::auto_ptr logonSid = GetLogonSid(token);
+ // Create a SECURITY_DESCRIPTOR that has both Low Integrity and allows access to all AppContainers
+ // This is needed since IE likes to jump out of Enhanced Protected Mode for specific pages (bing.com)
+ std::auto_ptr securityDescriptor = CreateSecurityDescriptor(logonSid.get());
+ securityAttributes.lpSecurityDescriptor = securityDescriptor.release();
+ sharedSecurityDescriptor.reset(static_cast(securityAttributes.lpSecurityDescriptor));
pipe = CreateNamedPipeW(pipeName.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &securityAttributes);
+
}
else
{
Index: src/shared/Utils.cpp
===================================================================
--- a/src/shared/Utils.cpp
+++ b/src/shared/Utils.cpp
@@ -25,6 +25,29 @@
return osvi.dwMajorVersion >= 6;
}
+bool IsAppContainersSupported()
+{
+ //Try to allocate SID of AppContainer and see if it's succesful
+ PSID allAppContainersSid = 0;
+ SID_IDENTIFIER_AUTHORITY applicationAuthority = SECURITY_APP_PACKAGE_AUTHORITY;
+ BOOL res = AllocateAndInitializeSid(&applicationAuthority,
+ SECURITY_BUILTIN_APP_PACKAGE_RID_COUNT,
+ SECURITY_APP_PACKAGE_BASE_RID,
+ SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE,
+ 0, 0, 0, 0, 0, 0,
+ &allAppContainersSid);
+
+ if (res == FALSE)
+ {
+ return false;
+ }
+ else
+ {
+ FreeSid(allAppContainersSid);
+ return true;
+ }
+}
+
std::string ToUtf8String(const std::wstring& str)
{
int length = static_cast(str.size());
Index: src/shared/Utils.h
===================================================================
--- a/src/shared/Utils.h
+++ b/src/shared/Utils.h
@@ -10,7 +10,28 @@
#define WM_UPDATE_CHECK_ERROR WM_APP+2
#define WM_DOWNLOADING_UPDATE WM_APP+3
+//
+// Application Package Authority.
+//
+
+#define SECURITY_APP_PACKAGE_AUTHORITY {0,0,0,0,0,15}
+
+#define SECURITY_APP_PACKAGE_BASE_RID (0x00000002L)
+#define SECURITY_BUILTIN_APP_PACKAGE_RID_COUNT (2L)
+#define SECURITY_APP_PACKAGE_RID_COUNT (8L)
+#define SECURITY_CAPABILITY_BASE_RID (0x00000003L)
+#define SECURITY_BUILTIN_CAPABILITY_RID_COUNT (2L)
+#define SECURITY_CAPABILITY_RID_COUNT (5L)
+
+//
+// Built-in Packages.
+//
+
+#define SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE (0x00000001L)
+
+
bool IsWindowsVistaOrLater();
+bool IsAppContainersSupported();
std::string ToUtf8String(const std::wstring& str);
std::wstring ToUtf16String(const std::string& str);
Index: test/CommunicationTest.cpp
===================================================================
--- a/test/CommunicationTest.cpp
+++ b/test/CommunicationTest.cpp
@@ -1,6 +1,6 @@
/*
* This file is part of Adblock Plus ,
- * Copyright (C) 2006-2013 Eyeo GmbH
+ * Copyright (C) 2006-2014 Eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Index: test/DictionaryTest.cpp
===================================================================
--- a/test/DictionaryTest.cpp
+++ b/test/DictionaryTest.cpp
@@ -1,6 +1,6 @@
/*
* This file is part of Adblock Plus ,
- * Copyright (C) 2006-2013 Eyeo GmbH
+ * Copyright (C) 2006-2014 Eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as