diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..dfe0770
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3c6208a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,295 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Typescript v1 declaration files
+typings/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
\ No newline at end of file
diff --git a/LICENSE.TXT b/LICENSE.TXT
new file mode 100644
index 0000000..d4d316e
--- /dev/null
+++ b/LICENSE.TXT
@@ -0,0 +1,28 @@
+Copyright (c) .NET Foundation and contributors.
+This software is released under the Microsoft Reciprocal License (MS-RL) (the "License"); you may not use the software except in compliance with the License.
+
+The text of the Microsoft Reciprocal License (MS-RL) can be found online at:
+ http://opensource.org/licenses/ms-rl
+
+
+Microsoft Reciprocal License (MS-RL)
+
+This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
+
+1. Definitions
+ The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
+ A "contribution" is the original software, or any additions or changes to the software.
+ A "contributor" is any person that distributes its contribution under this license.
+ "Licensed patents" are a contributor's patent claims that read directly on its contribution.
+
+2. Grant of Rights
+ (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
+ (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
+
+3. Conditions and Limitations
+ (A) Reciprocal Grants- For any file you distribute that contains code from the software (in source code or binary format), you must provide recipients the source code to that file along with a copy of this license, which license will govern that file. You may license other files that are entirely your own work and do not contain code from the software under any terms you choose.
+ (B) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
+ (C) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
+ (D) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
+ (E) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
+ (F) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
diff --git a/appveyor.cmd b/appveyor.cmd
new file mode 100644
index 0000000..63302e8
--- /dev/null
+++ b/appveyor.cmd
@@ -0,0 +1,13 @@
+@setlocal
+@pushd %~dp0
+
+msbuild -p:Configuration=Release;Platform=x86;PlatformToolset=v140_xp
+msbuild -p:Configuration=Release;Platform=x64;PlatformToolset=v140_xp
+
+msbuild -p:Configuration=Release;Platform=x86;PlatformToolset=v141_xp
+msbuild -p:Configuration=Release;Platform=x64;PlatformToolset=v141_xp
+
+msbuild -p:Configuration=Release -t:PackNativeNuget src\wcautil\wcautil.vcxproj
+
+@popd
+@endlocal
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..432653c
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,27 @@
+image: Visual Studio 2017
+
+version: 0.0.0.{build}
+configuration: Release
+
+environment:
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
+ NUGET_XMLDOC_MODE: skip
+
+before_build:
+ - nuget restore
+
+build_script:
+ - appveyor.cmd
+
+pull_requests:
+ do_not_increment_build_number: true
+
+nuget:
+ disable_publish_on_pr: true
+
+skip_tags: true
+
+artifacts:
+- path: build\Release\**\*.nupkg
+ name: nuget
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000..790be2b
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Cpp.Build.props b/src/Cpp.Build.props
new file mode 100644
index 0000000..1e4d4cb
--- /dev/null
+++ b/src/Cpp.Build.props
@@ -0,0 +1,100 @@
+
+
+
+
+
+ $(OutputPath)
+ $(BaseIntermediateOutputPath)$(Platform)\
+ $(OutputPath)$(Platform)\
+
+
+
+
+ $(DisableSpecificCompilerWarnings)
+ Level4
+ $(ProjectDir)inc;$(MSBuildProjectDirectory);$(IntDir);$(SqlCESdkIncludePath);$(ProjectAdditionalIncludeDirectories);%(AdditionalIncludeDirectories)
+ WIN32;_WINDOWS;_WIN32_MSI=500;_WIN32_WINNT=0x0501;$(ArmPreprocessorDefinitions);$(UnicodePreprocessorDefinitions);_CRT_STDIO_LEGACY_WIDE_SPECIFIERS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)
+ Use
+ precomp.h
+ StdCall
+ true
+ false
+ -YlprecompDefine
+ /Zc:threadSafeInit- %(AdditionalOptions)
+ true
+
+
+ $(ArmPreprocessorDefinitions);%(PreprocessorDefinitions)
+ $(ProjectAdditionalResourceIncludeDirectories);%(AdditionalIncludeDirectories)
+
+
+ $(OutDir);$(AdditionalMultiTargetLibraryPath);$(ProjectAdditionalLibraryDirectories);%(AdditionalLibraryDirectories)
+
+
+ $(ProjectSubSystem)
+ $(ProjectModuleDefinitionFile)
+ $(ResourceOnlyDll)
+ true
+ $(ProjectAdditionalLinkLibraries);advapi32.lib;comdlg32.lib;user32.lib;oleaut32.lib;gdi32.lib;shell32.lib;ole32.lib;version.lib;%(AdditionalDependencies)
+ $(OutDir);$(AdditionalMultiTargetLibraryPath);$(ArmLibraryDirectories);$(ProjectAdditionalLinkLibraryDirectories);%(AdditionalLibraryDirectories)
+ /IGNORE:4099 %(AdditionalOptions)
+
+
+
+
+
+ NoExtensions
+
+
+
+
+ CDecl
+
+
+
+
+ OldStyle
+ true
+ true
+
+
+
+
+ Disabled
+ EnableFastChecks
+ _DEBUG;DEBUG;%(PreprocessorDefinitions)
+ MultiThreadedDebug
+
+
+
+
+
+ MultiThreadedDebugDll
+
+
+
+
+ MinSpace
+ NDEBUG;%(PreprocessorDefinitions)
+ true
+ true
+ MultiThreaded
+
+
+ true
+ true
+
+
+
+
+
+ MultiThreadedDll
+
+
+
+
+ $(LinkKeyFile)
+ $(LinkDelaySign)
+
+
+
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 0000000..48ba462
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,21 @@
+
+
+
+
+
+ Debug
+ $(MSBuildThisFileDirectory)..\build\obj\$(MSBuildProjectName)\
+ $(MSBuildThisFileDirectory)..\build\$(Configuration)\
+
+ WiX Toolset Team
+ WiX Toolset
+ Copyright (c) .NET Foundation and contributors. All rights reserved.
+
+
+
+ $(MSBuildThisFileDirectory)..\..\
+
+
+
+
+
diff --git a/src/NativeMultiTargeting.Build.props b/src/NativeMultiTargeting.Build.props
new file mode 100644
index 0000000..98b1933
--- /dev/null
+++ b/src/NativeMultiTargeting.Build.props
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+ $(BaseIntermediateOutputPath)$(PlatformToolset)\$(PlatformTarget)\
+ $(OutputPath)$(PlatformToolset)\$(PlatformTarget)\
+
+
diff --git a/src/wcautil/build/WixToolset.WcaUtil.props b/src/wcautil/build/WixToolset.WcaUtil.props
new file mode 100644
index 0000000..71a9743
--- /dev/null
+++ b/src/wcautil/build/WixToolset.WcaUtil.props
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ $(MSBuildThisFileDirectory)native\include\;%(AdditionalIncludeDirectories)
+
+
+ $(MSBuildThisFileDirectory)native\include\;%(AdditionalIncludeDirectories)
+
+
+
+
+ $(MSBuildThisFileDirectory)native\lib\v140\$(PlatformTarget)\wcautil.lib;%(AdditionalDependencies)
+
+
+
+
+ $(MSBuildThisFileDirectory)native\lib\v141\$(PlatformTarget)\wcautil.lib;%(AdditionalDependencies)
+
+
+
diff --git a/src/wcautil/custommsierrors.h b/src/wcautil/custommsierrors.h
new file mode 100644
index 0000000..f149fb3
--- /dev/null
+++ b/src/wcautil/custommsierrors.h
@@ -0,0 +1,130 @@
+#pragma once
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+
+#define GLOBAL_ERROR_BASE 25501
+
+#define msierrSecureObjectsFailedCreateSD 25520
+#define msierrSecureObjectsFailedSet 25521
+#define msierrSecureObjectsUnknownType 25522
+
+#define msierrXmlFileFailedRead 25530
+#define msierrXmlFileFailedOpen 25531
+#define msierrXmlFileFailedSelect 25532
+#define msierrXmlFileFailedSave 25533
+
+#define msierrXmlConfigFailedRead 25540
+#define msierrXmlConfigFailedOpen 25541
+#define msierrXmlConfigFailedSelect 25542
+#define msierrXmlConfigFailedSave 25543
+
+#define msierrFirewallCannotConnect 25580
+
+//---------------------------------------------------------------------------
+// Server CustomAction Errors
+// SERVER range: 26001-26100
+#define SERVER_ERROR_BASE 26000
+
+#define msierrIISCannotConnect 26001
+#define msierrIISFailedReadWebSite 26002
+#define msierrIISFailedReadWebDirs 26003
+#define msierrIISFailedReadVDirs 26004
+#define msierrIISFailedReadFilters 26005
+#define msierrIISFailedReadAppPool 26006
+#define msierrIISFailedReadMimeMap 26007
+#define msierrIISFailedReadProp 26008
+#define msierrIISFailedReadWebSvcExt 26009
+#define msierrIISFailedReadWebError 26010
+#define msierrIISFailedReadHttpHeader 26011
+
+#define msierrIISFailedSchedTransaction 26031
+#define msierrIISFailedSchedInstallWebs 26032
+#define msierrIISFailedSchedInstallWebDirs 26033
+#define msierrIISFailedSchedInstallVDirs 26034
+#define msierrIISFailedSchedInstallFilters 26035
+#define msierrIISFailedSchedInstallAppPool 26036
+#define msierrIISFailedSchedInstallProp 26037
+#define msierrIISFailedSchedInstallWebSvcExt 26038
+
+#define msierrIISFailedSchedUninstallWebs 26051
+#define msierrIISFailedSchedUninstallWebDirs 26052
+#define msierrIISFailedSchedUninstallVDirs 26053
+#define msierrIISFailedSchedUninstallFilters 26054
+#define msierrIISFailedSchedUninstallAppPool 26055
+#define msierrIISFailedSchedUninstallProp 26056
+#define msierrIISFailedSchedUninstallWebSvcExt 26057
+
+#define msierrIISFailedStartTransaction 26101
+#define msierrIISFailedOpenKey 26102
+#define msierrIISFailedCreateKey 26103
+#define msierrIISFailedWriteData 26104
+#define msierrIISFailedCreateApp 26105
+#define msierrIISFailedDeleteKey 26106
+#define msierrIISFailedDeleteApp 26107
+#define msierrIISFailedDeleteValue 26108
+#define msierrIISFailedCommitInUse 26109
+
+#define msierrSQLFailedCreateDatabase 26201
+#define msierrSQLFailedDropDatabase 26202
+#define msierrSQLFailedConnectDatabase 26203
+#define msierrSQLFailedExecString 26204
+#define msierrSQLDatabaseAlreadyExists 26205
+
+#define msierrPERFMONFailedRegisterDLL 26251
+#define msierrPERFMONFailedUnregisterDLL 26252
+#define msierrInstallPerfCounterData 26253
+#define msierrUninstallPerfCounterData 26254
+
+#define msierrSMBFailedCreate 26301
+#define msierrSMBFailedDrop 26302
+
+#define msierrCERTFailedOpen 26351
+#define msierrCERTFailedAdd 26352
+
+#define msierrUSRFailedUserCreate 26401
+#define msierrUSRFailedUserCreatePswd 26402
+#define msierrUSRFailedUserGroupAdd 26403
+#define msierrUSRFailedUserCreateExists 26404
+#define msierrUSRFailedGrantLogonAsService 26405
+
+#define msierrDependencyMissingDependencies 26451
+#define msierrDependencyHasDependents 26452
+
+//--------------------------------------------------------------------------
+// Managed code CustomAction Errors
+// MANAGED range: 27000-27100
+#define MANAGED_ERROR_BASE 27000
+
+#define msierrDotNetRuntimeRequired 27000
+//---------------------------------------------------------------------------
+// Public CustomAction Errors
+// PUBLIC range: 28001-28100
+#define PUBLIC_ERROR_BASE 28000
+
+#define msierrComPlusCannotConnect 28001
+#define msierrComPlusPartitionReadFailed 28002
+#define msierrComPlusPartitionRoleReadFailed 28003
+#define msierrComPlusUserInPartitionRoleReadFailed 28004
+#define msierrComPlusPartitionUserReadFailed 28005
+#define msierrComPlusApplicationReadFailed 28006
+#define msierrComPlusApplicationRoleReadFailed 28007
+#define msierrComPlusUserInApplicationRoleReadFailed 28008
+#define msierrComPlusAssembliesReadFailed 28009
+#define msierrComPlusSubscriptionReadFailed 28010
+#define msierrComPlusPartitionDependency 28011
+#define msierrComPlusPartitionNotFound 28012
+#define msierrComPlusPartitionIdConflict 28013
+#define msierrComPlusPartitionNameConflict 28014
+#define msierrComPlusApplicationDependency 28015
+#define msierrComPlusApplicationNotFound 28016
+#define msierrComPlusApplicationIdConflict 28017
+#define msierrComPlusApplicationNameConflict 28018
+#define msierrComPlusApplicationRoleDependency 28019
+#define msierrComPlusApplicationRoleNotFound 28020
+#define msierrComPlusApplicationRoleConflict 28021
+#define msierrComPlusAssemblyDependency 28022
+#define msierrComPlusSubscriptionIdConflict 28023
+#define msierrComPlusSubscriptionNameConflict 28024
+#define msierrComPlusFailedLookupNames 28025
+
+#define msierrMsmqCannotConnect 28101
diff --git a/src/wcautil/exbinary.cpp b/src/wcautil/exbinary.cpp
new file mode 100644
index 0000000..5ff2421
--- /dev/null
+++ b/src/wcautil/exbinary.cpp
@@ -0,0 +1,142 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+
+//
+// Extracts the data from the Binary table row with the given ID into a buffer.
+//
+HRESULT WIXAPI WcaExtractBinaryToBuffer(
+ __in LPCWSTR wzBinaryId,
+ __out BYTE** pbData,
+ __out DWORD* pcbData
+ )
+{
+ HRESULT hr = S_OK;
+ LPWSTR pwzSql = NULL;
+ PMSIHANDLE hView;
+ PMSIHANDLE hRec;
+
+ // make sure we're not horked from the get-go
+ hr = WcaTableExists(L"Binary");
+ if (S_OK != hr)
+ {
+ if (SUCCEEDED(hr))
+ {
+ hr = E_UNEXPECTED;
+ }
+ ExitOnFailure(hr, "There is no Binary table.");
+ }
+
+ ExitOnNull(wzBinaryId, hr, E_INVALIDARG, "Binary ID cannot be null");
+ ExitOnNull(*wzBinaryId, hr, E_INVALIDARG, "Binary ID cannot be empty string");
+
+ hr = StrAllocFormatted(&pwzSql, L"SELECT `Data` FROM `Binary` WHERE `Name`=\'%ls\'", wzBinaryId);
+ ExitOnFailure(hr, "Failed to allocate Binary table query.");
+
+ hr = WcaOpenExecuteView(pwzSql, &hView);
+ ExitOnFailure(hr, "Failed to open view on Binary table");
+
+ hr = WcaFetchSingleRecord(hView, &hRec);
+ ExitOnFailure(hr, "Failed to retrieve request from Binary table");
+
+ hr = WcaGetRecordStream(hRec, 1, pbData, pcbData);
+ ExitOnFailure(hr, "Failed to read Binary.Data.");
+
+LExit:
+ ReleaseStr(pwzSql);
+
+ return hr;
+}
+
+//
+// Extracts the data from the Binary table row with the given ID into a file.
+//
+HRESULT WIXAPI WcaExtractBinaryToFile(
+ __in LPCWSTR wzBinaryId,
+ __in LPCWSTR wzPath
+ )
+{
+ HRESULT hr = S_OK;
+ BYTE* pbData = NULL;
+ DWORD cbData = 0;
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+
+ // grab the bits
+ hr = WcaExtractBinaryToBuffer(wzBinaryId, &pbData, &cbData);
+ ExitOnFailure(hr, "Failed to extract binary data: %ls", wzBinaryId);
+
+ // write 'em to the file
+ hFile = ::CreateFileW(wzPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (INVALID_HANDLE_VALUE == hFile)
+ {
+ ExitWithLastError(hr, "Failed to create file: %ls", wzPath);
+ }
+
+ DWORD cbWritten = 0;
+ if (!::WriteFile(hFile, pbData, cbData, &cbWritten, NULL))
+ {
+ ExitWithLastError(hr, "Failed to write data to file: %ls", wzPath);
+ }
+
+LExit:
+ ReleaseFile(hFile);
+ ReleaseMem(pbData);
+
+ return hr;
+}
+
+//
+// Extracts the data from the Binary table row with the given ID into a string.
+//
+HRESULT WIXAPI WcaExtractBinaryToString(
+ __in LPCWSTR wzBinaryId,
+ __deref_out_z LPWSTR* psczOutput,
+ __out WCA_ENCODING* encoding
+ )
+{
+ HRESULT hr = S_OK;
+ BYTE* pbData = NULL;
+ DWORD cbData = 0;
+
+ // grab the bits
+ hr = WcaExtractBinaryToBuffer(wzBinaryId, &pbData, &cbData);
+ ExitOnFailure(hr, "Failed to extract binary data: %ls", wzBinaryId);
+
+ // expand by a NULL character (or two) to make sure the buffer is null-terminated
+ cbData += 2;
+ pbData = reinterpret_cast(MemReAlloc(pbData, cbData, TRUE));
+ ExitOnNull(pbData, hr, E_OUTOFMEMORY, "Failed to expand binary buffer");
+
+ // Check for BOMs.
+ if (2 < cbData)
+ {
+ if ((0xFF == *pbData) && (0xFE == *(pbData + 1)))
+ {
+ *encoding = WCA_ENCODING_UTF_16;
+ hr = StrAllocString(psczOutput, reinterpret_cast(pbData), 0);
+ }
+ else if ((0xEF == *pbData) && (0xBB == *(pbData + 1)) && (0xBF == *(pbData + 2)))
+ {
+ *encoding = WCA_ENCODING_UTF_8;
+ hr = StrAllocStringAnsi(psczOutput, reinterpret_cast(pbData), 0, CP_UTF8);
+ }
+ else
+ {
+ *encoding = WCA_ENCODING_ANSI;
+ hr = StrAllocStringAnsi(psczOutput, reinterpret_cast(pbData), 0, CP_ACP);
+ }
+ ExitOnFailure(hr, "Failed to allocate string for binary buffer.");
+ }
+
+ // Free the byte buffer since it has been converted to a new UNICODE string, one way or another.
+ if (pbData)
+ {
+ WcaFreeStream(pbData);
+ pbData = NULL;
+ }
+
+LExit:
+ ReleaseMem(pbData);
+
+ return hr;
+}
diff --git a/src/wcautil/inc/wcalog.h b/src/wcautil/inc/wcalog.h
new file mode 100644
index 0000000..ffa3fa0
--- /dev/null
+++ b/src/wcautil/inc/wcalog.h
@@ -0,0 +1,14 @@
+#pragma once
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+BOOL WIXAPI IsVerboseLogging();
+HRESULT WIXAPI SetVerboseLoggingAtom(BOOL bValue);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/wcautil/inc/wcautil.h b/src/wcautil/inc/wcautil.h
new file mode 100644
index 0000000..8139a7c
--- /dev/null
+++ b/src/wcautil/inc/wcautil.h
@@ -0,0 +1,384 @@
+#pragma once
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define WIXAPI __stdcall
+#define ExitTrace WcaLogError
+
+#include "dutil.h"
+
+#define MessageExitOnLastError(x, e, s, ...) { x = ::GetLastError(); x = HRESULT_FROM_WIN32(x); if (FAILED(x)) { ExitTrace(x, "%s", s, __VA_ARGS__); WcaErrorMessage(e, x, MB_OK, -1, __VA_ARGS__); goto LExit; } }
+#define MessageExitOnFailure(x, e, s, ...) if (FAILED(x)) { ExitTrace(x, "%s", s, __VA_ARGS__); WcaErrorMessage(e, x, INSTALLMESSAGE_ERROR | MB_OK, -1, __VA_ARGS__); goto LExit; }
+#define MessageExitOnNullWithLastError(p, x, e, s, ...) if (NULL == p) { x = ::GetLastError(); x = HRESULT_FROM_WIN32(x); if (!FAILED(x)) { x = E_FAIL; } ExitTrace(x, "%s", s, __VA_ARGS__); WcaErrorMessage(e, x, MB_OK, -1, __VA_ARGS__); goto LExit; }
+
+// Generic action enum.
+typedef enum WCA_ACTION
+{
+ WCA_ACTION_NONE,
+ WCA_ACTION_INSTALL,
+ WCA_ACTION_UNINSTALL,
+} WCA_ACTION;
+
+typedef enum WCA_CASCRIPT
+{
+ WCA_CASCRIPT_SCHEDULED,
+ WCA_CASCRIPT_ROLLBACK,
+} WCA_CASCRIPT;
+
+typedef enum WCA_CASCRIPT_CLOSE
+{
+ WCA_CASCRIPT_CLOSE_PRESERVE,
+ WCA_CASCRIPT_CLOSE_DELETE,
+} WCA_CASCRIPT_CLOSE;
+
+typedef enum WCA_TODO
+{
+ WCA_TODO_UNKNOWN,
+ WCA_TODO_INSTALL,
+ WCA_TODO_UNINSTALL,
+ WCA_TODO_REINSTALL,
+} WCA_TODO;
+
+typedef struct WCA_CASCRIPT_STRUCT
+{
+ LPWSTR pwzScriptPath;
+ HANDLE hScriptFile;
+} *WCA_CASCRIPT_HANDLE;
+
+typedef enum WCA_ENCODING
+{
+ WCA_ENCODING_UNKNOWN,
+ WCA_ENCODING_UTF_16,
+ WCA_ENCODING_UTF_8,
+ WCA_ENCODING_ANSI,
+} WCA_ENCODING;
+
+void WIXAPI WcaGlobalInitialize(
+ __in HINSTANCE hInst
+ );
+void WIXAPI WcaGlobalFinalize();
+
+HRESULT WIXAPI WcaInitialize(
+ __in MSIHANDLE hInstall,
+ __in_z PCSTR szCustomActionLogName
+ );
+UINT WIXAPI WcaFinalize(
+ __in UINT iReturnValue
+ );
+BOOL WIXAPI WcaIsInitialized();
+
+MSIHANDLE WIXAPI WcaGetInstallHandle();
+MSIHANDLE WIXAPI WcaGetDatabaseHandle();
+
+const char* WIXAPI WcaGetLogName();
+
+void WIXAPI WcaSetReturnValue(
+ __in UINT iReturnValue
+ );
+BOOL WIXAPI WcaCancelDetected();
+
+#define LOG_BUFFER 2048
+typedef enum LOGLEVEL
+{
+ LOGMSG_TRACEONLY, // Never written to the log file (except in DEBUG builds)
+ LOGMSG_VERBOSE, // Written to log when LOGVERBOSE
+ LOGMSG_STANDARD // Written to log whenever informational logging is enabled
+} LOGLEVEL;
+
+void __cdecl WcaLog(
+ __in LOGLEVEL llv,
+ __in_z __format_string PCSTR fmt, ...
+ );
+BOOL WIXAPI WcaDisplayAssert(
+ __in LPCSTR sz
+ );
+void __cdecl WcaLogError(
+ __in HRESULT hr,
+ __in LPCSTR szMessage,
+ ...
+ );
+
+UINT WIXAPI WcaProcessMessage(
+ __in INSTALLMESSAGE eMessageType,
+ __in MSIHANDLE hRecord
+ );
+UINT __cdecl WcaErrorMessage(
+ __in int iError,
+ __in HRESULT hrError,
+ __in UINT uiType,
+ __in INT cArgs,
+ ...
+ );
+HRESULT WIXAPI WcaProgressMessage(
+ __in UINT uiCost,
+ __in BOOL fExtendProgressBar
+ );
+
+BOOL WIXAPI WcaIsInstalling(
+ __in INSTALLSTATE isInstalled,
+ __in INSTALLSTATE isAction
+ );
+BOOL WIXAPI WcaIsReInstalling(
+ __in INSTALLSTATE isInstalled,
+ __in INSTALLSTATE isAction
+ );
+BOOL WIXAPI WcaIsUninstalling(
+ __in INSTALLSTATE isInstalled,
+ __in INSTALLSTATE isAction
+ );
+
+HRESULT WIXAPI WcaSetComponentState(
+ __in_z LPCWSTR wzComponent,
+ __in INSTALLSTATE isState
+ );
+
+HRESULT WIXAPI WcaTableExists(
+ __in_z LPCWSTR wzTable
+ );
+
+HRESULT WIXAPI WcaOpenView(
+ __in_z LPCWSTR wzSql,
+ __out MSIHANDLE* phView
+ );
+HRESULT WIXAPI WcaExecuteView(
+ __in MSIHANDLE hView,
+ __in MSIHANDLE hRec
+ );
+HRESULT WIXAPI WcaOpenExecuteView(
+ __in_z LPCWSTR wzSql,
+ __out MSIHANDLE* phView
+ );
+HRESULT WIXAPI WcaFetchRecord(
+ __in MSIHANDLE hView,
+ __out MSIHANDLE* phRec
+ );
+HRESULT WIXAPI WcaFetchSingleRecord(
+ __in MSIHANDLE hView,
+ __out MSIHANDLE* phRec
+ );
+
+HRESULT WIXAPI WcaGetProperty(
+ __in_z LPCWSTR wzProperty,
+ __inout LPWSTR* ppwzData
+ );
+HRESULT WIXAPI WcaGetFormattedProperty(
+ __in_z LPCWSTR wzProperty,
+ __out LPWSTR* ppwzData
+ );
+HRESULT WIXAPI WcaGetFormattedString(
+ __in_z LPCWSTR wzString,
+ __out LPWSTR* ppwzData
+ );
+HRESULT WIXAPI WcaGetIntProperty(
+ __in_z LPCWSTR wzProperty,
+ __inout int* piData
+ );
+HRESULT WIXAPI WcaGetTargetPath(
+ __in_z LPCWSTR wzFolder,
+ __out LPWSTR* ppwzData
+ );
+HRESULT WIXAPI WcaSetProperty(
+ __in_z LPCWSTR wzPropertyName,
+ __in_z LPCWSTR wzPropertyValue
+ );
+HRESULT WIXAPI WcaSetIntProperty(
+ __in_z LPCWSTR wzPropertyName,
+ __in int nPropertyValue
+ );
+BOOL WIXAPI WcaIsPropertySet(
+ __in LPCSTR szProperty
+ );
+BOOL WIXAPI WcaIsUnicodePropertySet(
+ __in LPCWSTR wzProperty
+ );
+
+HRESULT WIXAPI WcaGetRecordInteger(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __inout int* piData
+ );
+HRESULT WIXAPI WcaGetRecordString(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __inout LPWSTR* ppwzData
+ );
+HRESULT WIXAPI WcaGetRecordFormattedInteger(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __out int* piData
+ );
+HRESULT WIXAPI WcaGetRecordFormattedString(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __inout LPWSTR* ppwzData
+ );
+
+HRESULT WIXAPI WcaAllocStream(
+ __deref_out_bcount_part(cbData, 0) BYTE** ppbData,
+ __in DWORD cbData
+ );
+HRESULT WIXAPI WcaFreeStream(
+ __in BYTE* pbData
+ );
+
+HRESULT WIXAPI WcaGetRecordStream(
+ __in MSIHANDLE hRecBinary,
+ __in UINT uiField,
+ __deref_out_bcount_full(*pcbData) BYTE** ppbData,
+ __out DWORD* pcbData
+ );
+HRESULT WIXAPI WcaSetRecordString(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __in_z LPCWSTR wzData
+ );
+HRESULT WIXAPI WcaSetRecordInteger(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __in int iValue
+ );
+
+HRESULT WIXAPI WcaDoDeferredAction(
+ __in_z LPCWSTR wzAction,
+ __in_z LPCWSTR wzCustomActionData,
+ __in UINT uiCost
+ );
+DWORD WIXAPI WcaCountOfCustomActionDataRecords(
+ __in_z LPCWSTR wzData
+ );
+
+HRESULT WIXAPI WcaReadStringFromCaData(
+ __deref_in LPWSTR* ppwzCustomActionData,
+ __deref_out_z LPWSTR* ppwzString
+ );
+HRESULT WIXAPI WcaReadIntegerFromCaData(
+ __deref_in LPWSTR* ppwzCustomActionData,
+ __out int* piResult
+ );
+HRESULT WIXAPI WcaReadStreamFromCaData(
+ __deref_in LPWSTR* ppwzCustomActionData,
+ __deref_out_bcount(*pcbData) BYTE** ppbData,
+ __out DWORD_PTR* pcbData
+ );
+HRESULT WIXAPI WcaWriteStringToCaData(
+ __in_z LPCWSTR wzString,
+ __deref_inout_z LPWSTR* ppwzCustomActionData
+ );
+HRESULT WIXAPI WcaWriteIntegerToCaData(
+ __in int i,
+ __deref_out_z_opt LPWSTR* ppwzCustomActionData
+ );
+HRESULT WIXAPI WcaWriteStreamToCaData(
+ __in_bcount(cbData) const BYTE* pbData,
+ __in DWORD cbData,
+ __deref_inout_z_opt LPWSTR* ppwzCustomActionData
+ );
+
+HRESULT __cdecl WcaAddTempRecord(
+ __inout MSIHANDLE* phTableView,
+ __inout MSIHANDLE* phColumns,
+ __in_z LPCWSTR wzTable,
+ __out_opt MSIDBERROR* pdbError,
+ __in UINT uiUniquifyColumn,
+ __in UINT cColumns,
+ ...
+ );
+
+HRESULT WIXAPI WcaDumpTable(
+ __in_z LPCWSTR wzTable
+ );
+
+HRESULT WIXAPI WcaDeferredActionRequiresReboot();
+BOOL WIXAPI WcaDidDeferredActionRequireReboot();
+
+HRESULT WIXAPI WcaCaScriptCreateKey(
+ __out LPWSTR* ppwzScriptKey
+ );
+
+HRESULT WIXAPI WcaCaScriptCreate(
+ __in WCA_ACTION action,
+ __in WCA_CASCRIPT script,
+ __in BOOL fImpersonated,
+ __in_z LPCWSTR wzScriptKey,
+ __in BOOL fAppend,
+ __out WCA_CASCRIPT_HANDLE* phScript
+ );
+
+HRESULT WIXAPI WcaCaScriptOpen(
+ __in WCA_ACTION action,
+ __in WCA_CASCRIPT script,
+ __in BOOL fImpersonated,
+ __in_z LPCWSTR wzScriptKey,
+ __out WCA_CASCRIPT_HANDLE* phScript
+ );
+
+void WIXAPI WcaCaScriptClose(
+ __in_opt WCA_CASCRIPT_HANDLE hScript,
+ __in WCA_CASCRIPT_CLOSE closeOperation
+ );
+
+HRESULT WIXAPI WcaCaScriptReadAsCustomActionData(
+ __in WCA_CASCRIPT_HANDLE hScript,
+ __out LPWSTR* ppwzCustomActionData
+ );
+
+HRESULT WIXAPI WcaCaScriptWriteString(
+ __in WCA_CASCRIPT_HANDLE hScript,
+ __in_z LPCWSTR wzValue
+ );
+
+HRESULT WIXAPI WcaCaScriptWriteNumber(
+ __in WCA_CASCRIPT_HANDLE hScript,
+ __in DWORD dwValue
+ );
+
+void WIXAPI WcaCaScriptFlush(
+ __in WCA_CASCRIPT_HANDLE hScript
+ );
+
+void WIXAPI WcaCaScriptCleanup(
+ __in_z LPCWSTR wzProductCode,
+ __in BOOL fImpersonated
+ );
+
+HRESULT WIXAPI QuietExec(
+ __inout_z LPWSTR wzCommand,
+ __in DWORD dwTimeout,
+ __in BOOL fLogCommand,
+ __in BOOL fLogOutput
+ );
+
+HRESULT WIXAPI QuietExecCapture(
+ __inout_z LPWSTR wzCommand,
+ __in DWORD dwTimeout,
+ __in BOOL fLogCommand,
+ __in BOOL fLogOutput,
+ __out_z_opt LPWSTR* psczOutput
+ );
+
+WCA_TODO WIXAPI WcaGetComponentToDo(
+ __in_z LPCWSTR wzComponentId
+ );
+
+HRESULT WIXAPI WcaExtractBinaryToBuffer(
+ __in LPCWSTR wzBinaryId,
+ __out BYTE** pbData,
+ __out DWORD* pcbData
+ );
+HRESULT WIXAPI WcaExtractBinaryToFile(
+ __in LPCWSTR wzBinaryId,
+ __in LPCWSTR wzPath
+ );
+HRESULT WIXAPI WcaExtractBinaryToString(
+ __in LPCWSTR wzBinaryId,
+ __deref_out_z LPWSTR* psczOutput,
+ __out WCA_ENCODING* encoding
+ );
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/wcautil/inc/wcawow64.h b/src/wcautil/inc/wcawow64.h
new file mode 100644
index 0000000..dd55f3f
--- /dev/null
+++ b/src/wcautil/inc/wcawow64.h
@@ -0,0 +1,20 @@
+#pragma once
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+
+#include "wcautil.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+HRESULT WIXAPI WcaInitializeWow64();
+BOOL WIXAPI WcaIsWow64Process();
+BOOL WIXAPI WcaIsWow64Initialized();
+HRESULT WIXAPI WcaDisableWow64FSRedirection();
+HRESULT WIXAPI WcaRevertWow64FSRedirection();
+HRESULT WIXAPI WcaFinalizeWow64();
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/wcautil/inc/wcawrapquery.h b/src/wcautil/inc/wcawrapquery.h
new file mode 100644
index 0000000..e08f1c3
--- /dev/null
+++ b/src/wcautil/inc/wcawrapquery.h
@@ -0,0 +1,130 @@
+#pragma once
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+
+#include "wcautil.h"
+
+// Enumerations
+typedef enum eWrapQueryAction
+{
+ wqaTableBegin = 1,
+ wqaTableFinish,
+ wqaRowBegin,
+ wqaRowFinish
+} eWrapQueryAction;
+
+typedef enum eColumnDataType
+{
+ cdtString = 1,
+ cdtInt,
+ cdtStream,
+ cdtUnknown
+} eColumnDataType;
+
+typedef enum eFormatMaskColumn
+{
+ efmcColumn1 = 1,
+ efmcColumn2 = 1 << 1,
+ efmcColumn3 = 1 << 2,
+ efmcColumn4 = 1 << 3,
+ efmcColumn5 = 1 << 4,
+ efmcColumn6 = 1 << 5,
+ efmcColumn7 = 1 << 6,
+ efmcColumn8 = 1 << 7,
+ efmcColumn9 = 1 << 8,
+ efmcColumn10 = 1 << 9,
+ efmcColumn11 = 1 << 10,
+ efmcColumn12 = 1 << 11,
+ efmcColumn13 = 1 << 12,
+ efmcColumn14 = 1 << 13,
+ efmcColumn15 = 1 << 14,
+ efmcColumn16 = 1 << 15,
+ efmcColumn17 = 1 << 16,
+ efmcColumn18 = 1 << 17,
+ efmcColumn19 = 1 << 18,
+ efmcColumn20 = 1 << 19,
+ efmcColumn21 = 1 << 20,
+ efmcColumn22 = 1 << 21,
+ efmcColumn23 = 1 << 22,
+ efmcColumn24 = 1 << 23,
+ efmcColumn25 = 1 << 24,
+ efmcColumn26 = 1 << 25,
+ efmcColumn27 = 1 << 26,
+ efmcColumn28 = 1 << 27,
+ efmcColumn29 = 1 << 28,
+ efmcColumn30 = 1 << 29,
+ efmcColumn31 = 1 << 30,
+ efmcColumn32 = 1 << 31,
+} eFormatMaskColumn;
+
+// Keeps track of the query instance for the reading CA (deferred CA)
+typedef struct WCA_WRAPQUERY_STRUCT
+{
+ // These are used to size our dynamic arrays below
+ DWORD dwColumns, dwRows, dwNextIndex;
+
+ // Dynamic arrays of column schema information
+ eColumnDataType *pcdtColumnType;
+ LPWSTR *ppwzColumnNames;
+
+ // Dynamic array of raw record data
+ MSIHANDLE *phRecords;
+} *WCA_WRAPQUERY_HANDLE;
+
+// Wrap a query
+// Setting the pfFormatMask enables control over which fields will be formatted, and which will be left unchanged
+// Setting dwComponentColumn to something other than 0xFFFFFFFF tells WcaWrapQuery to add two additional columns to the right side of the table
+// - ISInstalled and ISAction - which map to the ComponentState of the component (the component is found in the column specified)
+// Note that if a component is NULL, the component state columns will also be left null, and it will be up to the deferred CA to fail or ignore the case appropriately
+// Setting dwDirectoryColumn to something other than 0xFFFFFFFF tells WcaWrapQuery to add two more additional columns to the right side of the table
+// - SourcePath and TargetPath - which map to the Directory's Source and Target Path (the directory is found in the column specified)
+// Note that if a directory is NULL, the directory source/target path columns will also be left null, and it will be up to the deferred CA to fail or ignore the case appropriately
+HRESULT WIXAPI WcaWrapQuery(
+ __in_z LPCWSTR pwzQuery,
+ __inout LPWSTR * ppwzCustomActionData,
+ __in_opt DWORD dwFormatMask,
+ __in_opt DWORD dwComponentColumn,
+ __in_opt DWORD dwDirectoryColumn
+ );
+// This wraps an empty table query into the custom action data - this is a way to indicate to the deferred custom action that a necessary table doesn't exist, or its query returned no results
+HRESULT WIXAPI WcaWrapEmptyQuery(
+ __inout LPWSTR * ppwzCustomActionData
+ );
+
+// Open a new unwrap query operation, with data from the ppwzCustomActionData string
+HRESULT WIXAPI WcaBeginUnwrapQuery(
+ __out WCA_WRAPQUERY_HANDLE * phWrapQuery,
+ __inout LPWSTR * ppwzCustomActionData
+ );
+
+// Get the number of records in a query being unwrapped
+DWORD WIXAPI WcaGetQueryRecords(
+ __in const WCA_WRAPQUERY_HANDLE hWrapQuery
+ );
+
+// This function resets a query back to its first row, so that the next fetch returns the first record
+void WIXAPI WcaFetchWrappedReset(
+ __in WCA_WRAPQUERY_HANDLE hWrapQuery
+ );
+// Fetch the next record in this query
+// NOTE: the MSIHANDLE returned by this function should not be released, as it is the same handle used by the query object to maintain the item.
+// so, don't use this function with PMSIHANDLE objects!
+HRESULT WIXAPI WcaFetchWrappedRecord(
+ __in WCA_WRAPQUERY_HANDLE hWrapQuery,
+ __out MSIHANDLE* phRec
+ );
+
+// Fetch the next record in the query where the string value in column dwComparisonColumn equals the value pwzExpectedValue
+// NOTE: the MSIHANDLE returned by this function should not be released, as it is the same handle used by the query object to maintain the item.
+// so, don't use this function with PMSIHANDLE objects!
+HRESULT WIXAPI WcaFetchWrappedRecordWhereString(
+ __in WCA_WRAPQUERY_HANDLE hWrapQuery,
+ __in DWORD dwComparisonColumn,
+ __in_z LPCWSTR pwzExpectedValue,
+ __out MSIHANDLE* phRec
+ );
+
+// Release a query ID (frees memory, and frees the ID for a new query)
+void WIXAPI WcaFinishUnwrapQuery(
+ __in_opt WCA_WRAPQUERY_HANDLE hWrapQuery
+ );
diff --git a/src/wcautil/packages.config b/src/wcautil/packages.config
new file mode 100644
index 0000000..b11fe21
--- /dev/null
+++ b/src/wcautil/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/wcautil/precomp.h b/src/wcautil/precomp.h
new file mode 100644
index 0000000..1d41337
--- /dev/null
+++ b/src/wcautil/precomp.h
@@ -0,0 +1,19 @@
+#pragma once
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+
+#include
+#include
+#include
+#include
+
+const WCHAR MAGIC_MULTISZ_DELIM = 128;
+
+#include "wcautil.h"
+#include "inc\wcalog.h"
+#include "inc\wcawow64.h"
+#include "inc\wcawrapquery.h"
+#include "wiutil.h"
+#include "fileutil.h"
+#include "memutil.h"
+#include "strutil.h"
diff --git a/src/wcautil/qtexec.cpp b/src/wcautil/qtexec.cpp
new file mode 100644
index 0000000..19abfaf
--- /dev/null
+++ b/src/wcautil/qtexec.cpp
@@ -0,0 +1,340 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+
+#define OUTPUT_BUFFER 1024
+#define ONEMINUTE 60000
+
+static HRESULT CreatePipes(
+ __out HANDLE *phOutRead,
+ __out HANDLE *phOutWrite,
+ __out HANDLE *phErrWrite,
+ __out HANDLE *phInRead,
+ __out HANDLE *phInWrite
+ )
+{
+ Assert(phOutRead);
+ Assert(phOutWrite);
+ Assert(phErrWrite);
+ Assert(phInRead);
+ Assert(phInWrite);
+
+ HRESULT hr = S_OK;
+ SECURITY_ATTRIBUTES sa;
+ HANDLE hOutTemp = INVALID_HANDLE_VALUE;
+ HANDLE hInTemp = INVALID_HANDLE_VALUE;
+
+ HANDLE hOutRead = INVALID_HANDLE_VALUE;
+ HANDLE hOutWrite = INVALID_HANDLE_VALUE;
+ HANDLE hErrWrite = INVALID_HANDLE_VALUE;
+ HANDLE hInRead = INVALID_HANDLE_VALUE;
+ HANDLE hInWrite = INVALID_HANDLE_VALUE;
+
+ // Fill out security structure so we can inherit handles
+ ::ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa.bInheritHandle = TRUE;
+ sa.lpSecurityDescriptor = NULL;
+
+ // Create pipes
+ if (!::CreatePipe(&hOutTemp, &hOutWrite, &sa, 0))
+ {
+ ExitOnLastError(hr, "Failed to create output pipe");
+ }
+
+ if (!::CreatePipe(&hInRead, &hInTemp, &sa, 0))
+ {
+ ExitOnLastError(hr, "Failed to create input pipe");
+ }
+
+
+ // Duplicate output pipe so standard error and standard output write to
+ // the same pipe
+ if (!::DuplicateHandle(::GetCurrentProcess(), hOutWrite, ::GetCurrentProcess(), &hErrWrite, 0, TRUE, DUPLICATE_SAME_ACCESS))
+ {
+ ExitOnLastError(hr, "Failed to duplicate write handle");
+ }
+
+ // We need to create new output read and input write handles that are
+ // non inheritable. Otherwise it creates handles that can't be closed.
+ if (!::DuplicateHandle(::GetCurrentProcess(), hOutTemp, ::GetCurrentProcess(), &hOutRead, 0, FALSE, DUPLICATE_SAME_ACCESS))
+ {
+ ExitOnLastError(hr, "Failed to duplicate output pipe");
+ }
+
+ if (!::DuplicateHandle(::GetCurrentProcess(), hInTemp, ::GetCurrentProcess(), &hInWrite, 0, FALSE, DUPLICATE_SAME_ACCESS))
+ {
+ ExitOnLastError(hr, "Failed to duplicate input pipe");
+ }
+
+ // now that everything has succeeded, assign to the outputs
+ *phOutRead = hOutRead;
+ hOutRead = INVALID_HANDLE_VALUE;
+
+ *phOutWrite = hOutWrite;
+ hOutWrite = INVALID_HANDLE_VALUE;
+
+ *phErrWrite = hErrWrite;
+ hErrWrite = INVALID_HANDLE_VALUE;
+
+ *phInRead = hInRead;
+ hInRead = INVALID_HANDLE_VALUE;
+
+ *phInWrite = hInWrite;
+ hInWrite = INVALID_HANDLE_VALUE;
+
+LExit:
+ ReleaseFile(hOutRead);
+ ReleaseFile(hOutWrite);
+ ReleaseFile(hErrWrite);
+ ReleaseFile(hInRead);
+ ReleaseFile(hInWrite);
+ ReleaseFile(hOutTemp);
+ ReleaseFile(hInTemp);
+
+ return hr;
+}
+
+static HRESULT HandleOutput(
+ __in BOOL fLogOutput,
+ __in HANDLE hRead,
+ __out_z_opt LPWSTR* psczOutput
+ )
+{
+ BYTE* pBuffer = NULL;
+ LPWSTR szLog = NULL;
+ LPWSTR szTemp = NULL;
+ LPWSTR pEnd = NULL;
+ LPWSTR pNext = NULL;
+ LPWSTR sczEscaped = NULL;
+ LPSTR szWrite = NULL;
+ DWORD dwBytes = OUTPUT_BUFFER;
+ BOOL bFirst = TRUE;
+ BOOL bUnicode = TRUE;
+ HRESULT hr = S_OK;
+
+ // Get buffer for output
+ pBuffer = static_cast(MemAlloc(OUTPUT_BUFFER, FALSE));
+ ExitOnNull(pBuffer, hr, E_OUTOFMEMORY, "Failed to allocate buffer for output.");
+
+ while (0 != dwBytes)
+ {
+ ::ZeroMemory(pBuffer, OUTPUT_BUFFER);
+ if (!::ReadFile(hRead, pBuffer, OUTPUT_BUFFER - 1, &dwBytes, NULL) && GetLastError() != ERROR_BROKEN_PIPE)
+ {
+ ExitOnLastError(hr, "Failed to read from handle.");
+ }
+
+ if (fLogOutput)
+ {
+ // Check for UNICODE or ANSI output
+ if (bFirst)
+ {
+ if ((isgraph(pBuffer[0]) && isgraph(pBuffer[1])) ||
+ (isgraph(pBuffer[0]) && isspace(pBuffer[1])) ||
+ (isspace(pBuffer[0]) && isgraph(pBuffer[1])) ||
+ (isspace(pBuffer[0]) && isspace(pBuffer[1])))
+ {
+ bUnicode = FALSE;
+ }
+
+ bFirst = FALSE;
+ }
+
+ // Keep track of output
+ if (bUnicode)
+ {
+ hr = StrAllocConcat(&szLog, (LPCWSTR)pBuffer, 0);
+ ExitOnFailure(hr, "Failed to concatenate output strings.");
+
+ if (psczOutput)
+ {
+ hr = StrAllocConcat(psczOutput, (LPCWSTR)pBuffer, 0);
+ ExitOnFailure(hr, "Failed to concatenate output to return string.");
+ }
+ }
+ else
+ {
+ hr = StrAllocStringAnsi(&szTemp, (LPCSTR)pBuffer, 0, CP_OEMCP);
+ ExitOnFailure(hr, "Failed to allocate output string.");
+ hr = StrAllocConcat(&szLog, szTemp, 0);
+ ExitOnFailure(hr, "Failed to concatenate output strings.");
+
+ if (psczOutput)
+ {
+ hr = StrAllocConcat(psczOutput, szTemp, 0);
+ ExitOnFailure(hr, "Failed to concatenate output to return string.");
+ }
+ }
+
+ // Log each line of the output
+ pNext = szLog;
+ pEnd = wcschr(szLog, L'\r');
+ if (NULL == pEnd)
+ {
+ pEnd = wcschr(szLog, L'\n');
+ }
+ while (pEnd && *pEnd)
+ {
+ // Find beginning of next line
+ pEnd[0] = 0;
+ ++pEnd;
+ if ((pEnd[0] == L'\r') || (pEnd[0] == L'\n'))
+ {
+ ++pEnd;
+ }
+
+ // Log output
+ hr = StrAllocString(&sczEscaped, pNext, 0);
+ ExitOnFailure(hr, "Failed to allocate copy of string");
+
+ hr = StrReplaceStringAll(&sczEscaped, L"%", L"%%");
+ ExitOnFailure(hr, "Failed to escape percent signs in string");
+
+ hr = StrAnsiAllocString(&szWrite, sczEscaped, 0, CP_OEMCP);
+ ExitOnFailure(hr, "Failed to convert output to ANSI");
+ WcaLog(LOGMSG_STANDARD, szWrite);
+
+ // Next line
+ pNext = pEnd;
+ pEnd = wcschr(pNext, L'\r');
+ if (NULL == pEnd)
+ {
+ pEnd = wcschr(pNext, L'\n');
+ }
+ }
+
+ hr = StrAllocString(&szTemp, pNext, 0);
+ ExitOnFailure(hr, "Failed to allocate string");
+
+ hr = StrAllocString(&szLog, szTemp, 0);
+ ExitOnFailure(hr, "Failed to allocate string");
+ }
+ }
+
+ // Print any text that didn't end with a new line
+ if (szLog && *szLog)
+ {
+ hr = StrReplaceStringAll(&szLog, L"%", L"%%");
+ ExitOnFailure(hr, "Failed to escape percent signs in string");
+
+ hr = StrAnsiAllocString(&szWrite, szLog, 0, CP_OEMCP);
+ ExitOnFailure(hr, "Failed to convert output to ANSI");
+
+ WcaLog(LOGMSG_VERBOSE, szWrite);
+ }
+
+LExit:
+ ReleaseMem(pBuffer);
+
+ ReleaseStr(szLog);
+ ReleaseStr(szTemp);
+ ReleaseStr(szWrite);
+ ReleaseStr(sczEscaped);
+
+ return hr;
+}
+
+static HRESULT QuietExecImpl(
+ __inout_z LPWSTR wzCommand,
+ __in DWORD dwTimeout,
+ __in BOOL fLogCommand,
+ __in BOOL fLogOutput,
+ __out_z_opt LPWSTR* psczOutput
+ )
+{
+ HRESULT hr = S_OK;
+ PROCESS_INFORMATION oProcInfo;
+ STARTUPINFOW oStartInfo;
+ DWORD dwExitCode = ERROR_SUCCESS;
+ HANDLE hOutRead = INVALID_HANDLE_VALUE;
+ HANDLE hOutWrite = INVALID_HANDLE_VALUE;
+ HANDLE hErrWrite = INVALID_HANDLE_VALUE;
+ HANDLE hInRead = INVALID_HANDLE_VALUE;
+ HANDLE hInWrite = INVALID_HANDLE_VALUE;
+
+ memset(&oProcInfo, 0, sizeof(oProcInfo));
+ memset(&oStartInfo, 0, sizeof(oStartInfo));
+
+ // Create output redirect pipes
+ hr = CreatePipes(&hOutRead, &hOutWrite, &hErrWrite, &hInRead, &hInWrite);
+ ExitOnFailure(hr, "Failed to create output pipes");
+
+ // Set up startup structure
+ oStartInfo.cb = sizeof(STARTUPINFOW);
+ oStartInfo.dwFlags = STARTF_USESTDHANDLES;
+ oStartInfo.hStdInput = hInRead;
+ oStartInfo.hStdOutput = hOutWrite;
+ oStartInfo.hStdError = hErrWrite;
+
+ // Log command if we were asked to do so
+ if (fLogCommand)
+ {
+ WcaLog(LOGMSG_VERBOSE, "%ls", wzCommand);
+ }
+
+#pragma prefast(suppress:25028)
+ if (::CreateProcessW(NULL,
+ wzCommand, // command line
+ NULL, // security info
+ NULL, // thread info
+ TRUE, // inherit handles
+ ::GetPriorityClass(::GetCurrentProcess()) | CREATE_NO_WINDOW, // creation flags
+ NULL, // environment
+ NULL, // cur dir
+ &oStartInfo,
+ &oProcInfo))
+ {
+ ReleaseFile(oProcInfo.hThread);
+
+ // Close child output/input handles so it doesn't hang
+ ReleaseFile(hOutWrite);
+ ReleaseFile(hErrWrite);
+ ReleaseFile(hInRead);
+
+ // Log output if we were asked to do so; otherwise just read the output handle
+ HandleOutput(fLogOutput, hOutRead, psczOutput);
+
+ // Wait for everything to finish
+ ::WaitForSingleObject(oProcInfo.hProcess, dwTimeout);
+ if (!::GetExitCodeProcess(oProcInfo.hProcess, &dwExitCode))
+ {
+ dwExitCode = ERROR_SEM_IS_SET;
+ }
+
+ ReleaseFile(hOutRead);
+ ReleaseFile(hInWrite);
+ ReleaseFile(oProcInfo.hProcess);
+ }
+ else
+ {
+ ExitOnLastError(hr, "Command failed to execute.");
+ }
+
+ ExitOnWin32Error(dwExitCode, hr, "Command line returned an error.");
+
+LExit:
+ return hr;
+}
+
+
+HRESULT WIXAPI QuietExec(
+ __inout_z LPWSTR wzCommand,
+ __in DWORD dwTimeout,
+ __in BOOL fLogCommand,
+ __in BOOL fLogOutput
+ )
+{
+ return QuietExecImpl(wzCommand, dwTimeout, fLogCommand, fLogOutput, NULL);
+}
+
+HRESULT WIXAPI QuietExecCapture(
+ __inout_z LPWSTR wzCommand,
+ __in DWORD dwTimeout,
+ __in BOOL fLogCommand,
+ __in BOOL fLogOutput,
+ __out_z_opt LPWSTR* psczOutput
+ )
+{
+ return QuietExecImpl(wzCommand, dwTimeout, fLogCommand, fLogOutput, psczOutput);
+}
diff --git a/src/wcautil/wcalog.cpp b/src/wcautil/wcalog.cpp
new file mode 100644
index 0000000..fa969bf
--- /dev/null
+++ b/src/wcautil/wcalog.cpp
@@ -0,0 +1,251 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+
+/********************************************************************
+ IsVerboseLoggingPolicy() - internal helper function to detect if
+ policy is set for verbose logging. Does
+ not require database access.
+********************************************************************/
+static BOOL IsVerboseLoggingPolicy()
+{
+ BOOL fVerbose = FALSE;
+ HKEY hkey = NULL;
+ WCHAR rgwc[16] = { 0 };
+ DWORD cb = sizeof(rgwc);
+ if (ERROR_SUCCESS == ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Policies\\Microsoft\\Windows\\Installer", 0, KEY_QUERY_VALUE, &hkey))
+ {
+ if (ERROR_SUCCESS == ::RegQueryValueExW(hkey, L"Logging", 0, NULL, reinterpret_cast(rgwc), &cb))
+ {
+ for (LPCWSTR pwc = rgwc; (cb / sizeof(WCHAR)) > static_cast(pwc - rgwc) && *pwc; pwc++)
+ {
+ if (L'v' == *pwc || L'V' == *pwc)
+ {
+ fVerbose = TRUE;
+ break;
+ }
+ }
+ }
+
+ ::RegCloseKey(hkey);
+ }
+ return fVerbose;
+}
+
+/********************************************************************
+ IsVerboseLogging() - internal helper function to detect if doing
+ verbose logging. Checks:
+ 1. LOGVERBOSE property.
+ 2. MsiLogging property contains 'v'
+ 3. Policy from registry.
+
+ Requires database access.
+********************************************************************/
+BOOL WIXAPI IsVerboseLogging()
+{
+ static int iVerbose = -1;
+ LPWSTR pwzMsiLogging = NULL;
+
+ if (0 > iVerbose)
+ {
+ iVerbose = WcaIsPropertySet("LOGVERBOSE");
+ if (0 == iVerbose)
+ {
+ // if the property wasn't set, check the MsiLogging property (MSI 4.0+)
+ HRESULT hr = WcaGetProperty(L"MsiLogging", &pwzMsiLogging);
+ ExitOnFailure(hr, "failed to get MsiLogging property");
+ int cchMsiLogging = lstrlenW(pwzMsiLogging);
+ if (0 < cchMsiLogging)
+ {
+ for (int i = 0; i < cchMsiLogging; i++)
+ {
+ if (L'v' == pwzMsiLogging[i] || L'V' == pwzMsiLogging[i])
+ {
+ iVerbose = 1;
+ break;
+ }
+ }
+ }
+
+ // last chance: Check the registry to see if the logging policy was turned on
+ if (0 == iVerbose && IsVerboseLoggingPolicy())
+ {
+ iVerbose = 1;
+ }
+ }
+ }
+
+LExit:
+ ReleaseStr(pwzMsiLogging);
+ Assert(iVerbose >= 0);
+ return (BOOL)iVerbose;
+}
+
+/********************************************************************
+ SetVerboseLoggingAtom() - Sets one of two global Atoms to specify
+ if the install should do verbose logging.
+ Communicates the verbose setting to
+ deferred CAs.
+ Set a negative case atom so that we can
+ distinguish between an unset atom and the
+ non-verbose case. This helps prevent the
+ expensive regkey lookup for non-verbose.
+********************************************************************/
+HRESULT WIXAPI SetVerboseLoggingAtom(BOOL bValue)
+{
+ HRESULT hr = S_OK;
+ ATOM atomVerbose = 0;
+
+ atomVerbose = ::GlobalFindAtomW(L"WcaVerboseLogging");
+ if (0 == atomVerbose && bValue)
+ {
+ atomVerbose = ::GlobalAddAtomW(L"WcaVerboseLogging");
+ ExitOnNullWithLastError(atomVerbose, hr, "Failed to create WcaVerboseLogging global atom.");
+ }
+ else if (0 != atomVerbose && !bValue)
+ {
+ ::SetLastError(ERROR_SUCCESS);
+ ::GlobalDeleteAtom(atomVerbose);
+ ExitOnLastError(hr, "Failed to delete WcaVerboseLogging global atom.");
+ }
+
+ atomVerbose = ::GlobalFindAtomW(L"WcaNotVerboseLogging");
+ if (0 == atomVerbose && !bValue)
+ {
+ atomVerbose = ::GlobalAddAtomW(L"WcaNotVerboseLogging");
+ ExitOnNullWithLastError(atomVerbose, hr, "Failed to create WcaNotVerboseLogging global atom.");
+ }
+ else if (0 != atomVerbose && bValue)
+ {
+ ::SetLastError(ERROR_SUCCESS);
+ ::GlobalDeleteAtom(atomVerbose);
+ ExitOnLastError(hr, "Failed to delete WcaNotVerboseLogging global atom.");
+ }
+
+LExit:
+ return hr;
+}
+
+/********************************************************************
+ IsVerboseLoggingLite() - internal helper function to detect if atom was
+ previously set to specify verbose logging.
+ Falls back on policy for an installer that is
+ unable to set the atom (no immediate CAs).
+
+ Does not require database access.
+********************************************************************/
+static BOOL IsVerboseLoggingLite()
+{
+ ATOM atomVerbose = ::GlobalFindAtomW(L"WcaVerboseLogging");
+ if (0 != atomVerbose)
+ {
+ return TRUE;
+ }
+
+ atomVerbose = ::GlobalFindAtomW(L"WcaNotVerboseLogging");
+ if (0 != atomVerbose)
+ {
+ return FALSE;
+ }
+
+ return IsVerboseLoggingPolicy();
+}
+
+/********************************************************************
+ WcaLog() - outputs trace and log info
+
+*******************************************************************/
+extern "C" void __cdecl WcaLog(
+ __in LOGLEVEL llv,
+ __in_z __format_string PCSTR fmt,
+ ...
+ )
+{
+ static char szFmt[LOG_BUFFER];
+ static char szBuf[LOG_BUFFER];
+ static bool fInLogPrint = false;
+
+ // prevent re-entrant logprints. (recursion issues between assert/logging code)
+ if (fInLogPrint)
+ return;
+ fInLogPrint = true;
+
+ if (LOGMSG_STANDARD == llv ||
+ (LOGMSG_VERBOSE == llv && IsVerboseLoggingLite())
+#ifdef DEBUG
+ || LOGMSG_TRACEONLY == llv
+#endif
+ )
+ {
+ va_list args;
+ va_start(args, fmt);
+
+ LPCSTR szLogName = WcaGetLogName();
+ if (szLogName[0] != 0)
+ StringCchPrintfA(szFmt, countof(szFmt), "%s: %s", szLogName, fmt);
+ else
+ StringCchCopyA(szFmt, countof(szFmt), fmt);
+
+ StringCchVPrintfA(szBuf, countof(szBuf), szFmt, args);
+ va_end(args);
+
+#ifdef DEBUG
+ // always write to the log in debug
+#else
+ if (llv == LOGMSG_STANDARD || (llv == LOGMSG_VERBOSE && IsVerboseLoggingLite()))
+#endif
+ {
+ PMSIHANDLE hrec = MsiCreateRecord(1);
+
+ ::MsiRecordSetStringA(hrec, 0, szBuf);
+ // TODO: Recursion on failure. May not be safe to assert from here.
+ WcaProcessMessage(INSTALLMESSAGE_INFO, hrec);
+ }
+
+#if DEBUG
+ StringCchCatA(szBuf, countof(szBuf), "\n");
+ OutputDebugStringA(szBuf);
+#endif
+ }
+
+ fInLogPrint = false;
+ return;
+}
+
+
+/********************************************************************
+ WcaDisplayAssert() - called before Assert() dialog shows
+
+ NOTE: writes the assert string to the MSI log
+********************************************************************/
+extern "C" BOOL WIXAPI WcaDisplayAssert(
+ __in LPCSTR sz
+ )
+{
+ WcaLog(LOGMSG_STANDARD, "Debug Assert Message: %s", sz);
+ return TRUE;
+}
+
+
+/********************************************************************
+ WcaLogError() - called before ExitOnXXX() macro exists the function
+
+ NOTE: writes the hresult and error string to the MSI log
+********************************************************************/
+extern "C" void WcaLogError(
+ __in HRESULT hr,
+ __in LPCSTR szMessage,
+ ...
+ )
+{
+ char szBuffer[LOG_BUFFER];
+ va_list dots;
+
+ va_start(dots, szMessage);
+ StringCchVPrintfA(szBuffer, countof(szBuffer), szMessage, dots);
+ va_end(dots);
+
+ // log the message if using Wca common layer
+ if (WcaIsInitialized())
+ WcaLog(LOGMSG_STANDARD, "Error 0x%x: %s", hr, szBuffer);
+}
diff --git a/src/wcautil/wcascript.cpp b/src/wcautil/wcascript.cpp
new file mode 100644
index 0000000..b662985
--- /dev/null
+++ b/src/wcautil/wcascript.cpp
@@ -0,0 +1,447 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+
+
+static HRESULT CaScriptFileName(
+ __in WCA_ACTION action,
+ __in WCA_CASCRIPT script,
+ __in BOOL fImpersonated,
+ __in_z LPCWSTR wzScriptKey,
+ __out LPWSTR* pwzScriptName
+ );
+
+
+/********************************************************************
+ WcaCaScriptCreateKey() - creates a unique script key for this
+ CustomAction.
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaCaScriptCreateKey(
+ __out LPWSTR* ppwzScriptKey
+ )
+{
+ AssertSz(WcaIsInitialized(), "WcaInitialize() should have been called before calling this function.");
+ HRESULT hr = S_OK;
+
+ hr = StrAllocStringAnsi(ppwzScriptKey, WcaGetLogName(), 0, CP_ACP);
+ ExitOnFailure(hr, "Failed to create script key.");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+ WcaCaScriptCreate() - creates the appropriate script for this
+ CustomAction Script Key.
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaCaScriptCreate(
+ __in WCA_ACTION action,
+ __in WCA_CASCRIPT script,
+ __in BOOL fImpersonated,
+ __in_z LPCWSTR wzScriptKey,
+ __in BOOL fAppend,
+ __out WCA_CASCRIPT_HANDLE* phScript
+ )
+{
+ HRESULT hr = S_OK;
+ LPWSTR pwzScriptPath = NULL;
+ HANDLE hScriptFile = INVALID_HANDLE_VALUE;
+
+ hr = CaScriptFileName(action, script, fImpersonated, wzScriptKey, &pwzScriptPath);
+ ExitOnFailure(hr, "Failed to calculate script file name.");
+
+ hScriptFile = ::CreateFileW(pwzScriptPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, fAppend ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+ if (INVALID_HANDLE_VALUE == hScriptFile)
+ {
+ ExitWithLastError(hr, "Failed to open CaScript: %ls", pwzScriptPath);
+ }
+
+ if (fAppend && INVALID_SET_FILE_POINTER == ::SetFilePointer(hScriptFile, 0, NULL, FILE_END))
+ {
+ ExitWithLastError(hr, "Failed to seek to end of file.");
+ }
+
+ *phScript = static_cast(MemAlloc(sizeof(WCA_CASCRIPT_STRUCT), TRUE));
+ ExitOnNull(*phScript, hr, E_OUTOFMEMORY, "Failed to allocate space for cascript handle.");
+
+ (*phScript)->pwzScriptPath = pwzScriptPath;
+ pwzScriptPath = NULL;
+ (*phScript)->hScriptFile = hScriptFile;
+ hScriptFile = INVALID_HANDLE_VALUE;
+
+LExit:
+ if (INVALID_HANDLE_VALUE != hScriptFile)
+ {
+ ::CloseHandle(hScriptFile);
+ }
+
+ ReleaseStr(pwzScriptPath);
+ return hr;
+}
+
+
+/********************************************************************
+ WcaCaScriptOpen() - opens the appropriate script for this CustomAction
+ Script Key.
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaCaScriptOpen(
+ __in WCA_ACTION action,
+ __in WCA_CASCRIPT script,
+ __in BOOL fImpersonated,
+ __in_z LPCWSTR wzScriptKey,
+ __out WCA_CASCRIPT_HANDLE* phScript
+ )
+{
+ HRESULT hr = S_OK;
+ LPWSTR pwzScriptPath = NULL;
+ HANDLE hScriptFile = INVALID_HANDLE_VALUE;
+
+ hr = CaScriptFileName(action, script, fImpersonated, wzScriptKey, &pwzScriptPath);
+ ExitOnFailure(hr, "Failed to calculate script file name.");
+
+ hScriptFile = ::CreateFileW(pwzScriptPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+ if (INVALID_HANDLE_VALUE == hScriptFile)
+ {
+ ExitWithLastError(hr, "Failed to open CaScript: %ls", pwzScriptPath);
+ }
+
+ *phScript = static_cast(MemAlloc(sizeof(WCA_CASCRIPT_STRUCT), TRUE));
+ ExitOnNull(*phScript, hr, E_OUTOFMEMORY, "Failed to allocate space for cascript handle.");
+
+ (*phScript)->pwzScriptPath = pwzScriptPath;
+ pwzScriptPath = NULL;
+ (*phScript)->hScriptFile = hScriptFile;
+ hScriptFile = INVALID_HANDLE_VALUE;
+
+LExit:
+ if (INVALID_HANDLE_VALUE != hScriptFile)
+ {
+ ::CloseHandle(hScriptFile);
+ }
+
+ ReleaseStr(pwzScriptPath);
+ return hr;
+}
+
+
+/********************************************************************
+ WcaCaScriptClose() - closes an open script handle.
+
+********************************************************************/
+extern "C" void WIXAPI WcaCaScriptClose(
+ __in_opt WCA_CASCRIPT_HANDLE hScript,
+ __in WCA_CASCRIPT_CLOSE closeOperation
+ )
+{
+ if (hScript)
+ {
+ if (INVALID_HANDLE_VALUE != hScript->hScriptFile)
+ {
+ ::CloseHandle(hScript->hScriptFile);
+ }
+
+ if (hScript->pwzScriptPath)
+ {
+ if (WCA_CASCRIPT_CLOSE_DELETE == closeOperation)
+ {
+ ::DeleteFileW(hScript->pwzScriptPath);
+ }
+
+ StrFree(hScript->pwzScriptPath);
+ }
+
+ MemFree(hScript);
+ }
+}
+
+
+/********************************************************************
+ WcaCaScriptReadAsCustomActionData() - read the ca script into a format
+ that is useable by other CA data
+ functions.
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaCaScriptReadAsCustomActionData(
+ __in WCA_CASCRIPT_HANDLE hScript,
+ __out LPWSTR* ppwzCustomActionData
+ )
+{
+ HRESULT hr = S_OK;
+ LARGE_INTEGER liScriptSize = { 0 };
+ BYTE* pbData = NULL;
+ DWORD cbData = 0;
+
+ if (!::GetFileSizeEx(hScript->hScriptFile, &liScriptSize))
+ {
+ ExitWithLastError(hr, "Failed to get size of ca script file.");
+ }
+
+ if (0 != liScriptSize.HighPart || 0 != (liScriptSize.LowPart % sizeof(WCHAR)))
+ {
+ hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
+ ExitOnRootFailure(hr, "Invalid data read from ca script.");
+ }
+
+ cbData = liScriptSize.LowPart;
+ if (cbData)
+ {
+ pbData = static_cast(MemAlloc(cbData, TRUE));
+ ExitOnNull(pbData, hr, E_OUTOFMEMORY, "Failed to allocate memory to read in ca script.");
+
+ if (INVALID_SET_FILE_POINTER == ::SetFilePointer(hScript->hScriptFile, 0, NULL, FILE_BEGIN))
+ {
+ ExitWithLastError(hr, "Failed to reset to beginning of ca script.");
+ }
+
+ DWORD cbTotalRead = 0;
+ DWORD cbRead = 0;
+ do
+ {
+ if (!::ReadFile(hScript->hScriptFile, pbData + cbTotalRead, cbData - cbTotalRead, &cbRead, NULL))
+ {
+ ExitWithLastError(hr, "Failed to read from ca script.");
+ }
+
+ cbTotalRead += cbRead;
+ } while (cbRead && cbTotalRead < cbData);
+
+ if (cbTotalRead != cbData)
+ {
+ hr = E_UNEXPECTED;
+ ExitOnFailure(hr, "Failed to completely read ca script.");
+ }
+ }
+
+ // Add one to the allocated space because the data stored in the script is not
+ // null terminated. After copying the memory over, we'll ensure the string is
+ // null terminated.
+ DWORD cchData = cbData / sizeof(WCHAR) + 1;
+ hr = StrAlloc(ppwzCustomActionData, cchData);
+ ExitOnFailure(hr, "Failed to copy ca script.");
+
+ if (cbData)
+ {
+ CopyMemory(*ppwzCustomActionData, pbData, cbData);
+ }
+
+ (*ppwzCustomActionData)[cchData - 1] = L'\0';
+
+LExit:
+ ReleaseMem(pbData);
+ return hr;
+}
+
+
+/********************************************************************
+ WcaCaScriptWriteString() - writes a string to the ca script.
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaCaScriptWriteString(
+ __in WCA_CASCRIPT_HANDLE hScript,
+ __in_z LPCWSTR wzValue
+ )
+{
+ HRESULT hr = S_OK;
+ DWORD cbFile = 0;
+ DWORD cbWrite = 0;
+ DWORD cbTotalWritten = 0;
+ WCHAR delim[] = { MAGIC_MULTISZ_DELIM }; // magic char followed by NULL terminator
+
+ cbFile = ::SetFilePointer(hScript->hScriptFile, 0, NULL, FILE_END);
+ if (INVALID_SET_FILE_POINTER == cbFile)
+ {
+ ExitWithLastError(hr, "Failed to move file pointer to end of file.");
+ }
+
+ // If there is existing data in the file, append on the magic delimeter
+ // before adding our new data on the end of the file.
+ if (0 < cbFile)
+ {
+ cbWrite = sizeof(delim);
+ cbTotalWritten = 0;
+ while (cbTotalWritten < cbWrite)
+ {
+ DWORD cbWritten = 0;
+ if (!::WriteFile(hScript->hScriptFile, reinterpret_cast(delim) + cbTotalWritten, cbWrite - cbTotalWritten, &cbWritten, NULL))
+ {
+ ExitWithLastError(hr, "Failed to write data to ca script.");
+ }
+
+ cbTotalWritten += cbWritten;
+ }
+ }
+
+ cbWrite = lstrlenW(wzValue) * sizeof(WCHAR);
+ cbTotalWritten = 0;
+ while (cbTotalWritten < cbWrite)
+ {
+ DWORD cbWritten = 0;
+ if (!::WriteFile(hScript->hScriptFile, reinterpret_cast(wzValue) + cbTotalWritten, cbWrite - cbTotalWritten, &cbWritten, NULL))
+ {
+ ExitWithLastError(hr, "Failed to write data to ca script.");
+ }
+
+ cbTotalWritten += cbWritten;
+ }
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+ WcaCaScriptWriteNumber() - writes a number to the ca script.
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaCaScriptWriteNumber(
+ __in WCA_CASCRIPT_HANDLE hScript,
+ __in DWORD dwValue
+ )
+{
+ HRESULT hr = S_OK;
+ WCHAR wzBuffer[13] = { 0 };
+
+ hr = ::StringCchPrintfW(wzBuffer, countof(wzBuffer), L"%u", dwValue);
+ ExitOnFailure(hr, "Failed to convert number into string.");
+
+ hr = WcaCaScriptWriteString(hScript, wzBuffer);
+ ExitOnFailure(hr, "Failed to write number to script.");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+ WcaCaScriptFlush() - best effort function to get script written to
+ disk.
+
+********************************************************************/
+extern "C" void WIXAPI WcaCaScriptFlush(
+ __in WCA_CASCRIPT_HANDLE hScript
+ )
+{
+ ::FlushFileBuffers(hScript->hScriptFile);
+}
+
+
+/********************************************************************
+ WcaCaScriptCleanup() - best effort clean-up of any cascripts left
+ over from this install/uninstall.
+
+********************************************************************/
+extern "C" void WIXAPI WcaCaScriptCleanup(
+ __in_z LPCWSTR wzProductCode,
+ __in BOOL fImpersonated
+ )
+{
+ HRESULT hr = S_OK;
+ WCHAR wzTempPath[MAX_PATH];
+ LPWSTR pwzWildCardPath = NULL;
+ WIN32_FIND_DATAW fd = { 0 };
+ HANDLE hff = INVALID_HANDLE_VALUE;
+ LPWSTR pwzDeletePath = NULL;
+
+ if (fImpersonated)
+ {
+ if (!::GetTempPathW(countof(wzTempPath), wzTempPath))
+ {
+ ExitWithLastError(hr, "Failed to get temp path.");
+ }
+ }
+ else
+ {
+ if (!::GetWindowsDirectoryW(wzTempPath, countof(wzTempPath)))
+ {
+ ExitWithLastError(hr, "Failed to get windows path.");
+ }
+
+ hr = ::StringCchCatW(wzTempPath, countof(wzTempPath), L"\\Installer\\");
+ ExitOnFailure(hr, "Failed to concat Installer directory on windows path string.");
+ }
+
+ hr = StrAllocFormatted(&pwzWildCardPath, L"%swix%s.*.???", wzTempPath, wzProductCode);
+ ExitOnFailure(hr, "Failed to allocate wildcard path to ca scripts.");
+
+ hff = ::FindFirstFileW(pwzWildCardPath, &fd);
+ if (INVALID_HANDLE_VALUE == hff)
+ {
+ ExitWithLastError(hr, "Failed to find files with pattern: %ls", pwzWildCardPath);
+ }
+
+ do
+ {
+ hr = StrAllocFormatted(&pwzDeletePath, L"%s%s", wzTempPath, fd.cFileName);
+ if (SUCCEEDED(hr))
+ {
+ if (!::DeleteFileW(pwzDeletePath))
+ {
+ DWORD er = ::GetLastError();
+ WcaLog(LOGMSG_VERBOSE, "Failed to clean up CAScript file: %ls, er: %d", fd.cFileName, er);
+ }
+ }
+ else
+ {
+ WcaLog(LOGMSG_VERBOSE, "Failed to allocate path to clean up CAScript file: %ls, hr: 0x%x", fd.cFileName, hr);
+ }
+ } while(::FindNextFileW(hff, &fd));
+
+LExit:
+ if (INVALID_HANDLE_VALUE == hff)
+ {
+ ::FindClose(hff);
+ }
+
+ ReleaseStr(pwzDeletePath);
+ ReleaseStr(pwzWildCardPath);
+ return;
+}
+
+
+static HRESULT CaScriptFileName(
+ __in WCA_ACTION action,
+ __in WCA_CASCRIPT script,
+ __in BOOL fImpersonated,
+ __in_z LPCWSTR wzScriptKey,
+ __out LPWSTR* ppwzScriptName
+ )
+{
+ HRESULT hr = S_OK;
+ WCHAR wzTempPath[MAX_PATH];
+ LPWSTR pwzProductCode = NULL;
+ WCHAR chInstallOrUninstall = action == WCA_ACTION_INSTALL ? L'i' : L'u';
+ WCHAR chScheduledOrRollback = script == WCA_CASCRIPT_SCHEDULED ? L's' : L'r';
+ WCHAR chUserOrMachine = fImpersonated ? L'u' : L'm';
+
+ if (fImpersonated)
+ {
+ if (!::GetTempPathW(countof(wzTempPath), wzTempPath))
+ {
+ ExitWithLastError(hr, "Failed to get temp path.");
+ }
+ }
+ else
+ {
+ if (!::GetWindowsDirectoryW(wzTempPath, countof(wzTempPath)))
+ {
+ ExitWithLastError(hr, "Failed to get windows path.");
+ }
+
+ hr = ::StringCchCatW(wzTempPath, countof(wzTempPath), L"\\Installer\\");
+ ExitOnFailure(hr, "Failed to concat Installer directory on windows path string.");
+ }
+
+ hr = WcaGetProperty(L"ProductCode", &pwzProductCode);
+ ExitOnFailure(hr, "Failed to get ProductCode.");
+
+ hr = StrAllocFormatted(ppwzScriptName, L"%swix%s.%s.%c%c%c", wzTempPath, pwzProductCode, wzScriptKey, chScheduledOrRollback, chUserOrMachine, chInstallOrUninstall);
+ ExitOnFailure(hr, "Failed to allocate path to ca script.");
+
+LExit:
+ ReleaseStr(pwzProductCode);
+ return hr;
+}
diff --git a/src/wcautil/wcautil.cpp b/src/wcautil/wcautil.cpp
new file mode 100644
index 0000000..ce5ef15
--- /dev/null
+++ b/src/wcautil/wcautil.cpp
@@ -0,0 +1,216 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+
+// globals
+HMODULE g_hInstCADLL;
+
+// statics
+static BOOL s_fInitialized;
+static MSIHANDLE s_hInstall;
+static MSIHANDLE s_hDatabase;
+static char s_szCustomActionLogName[32];
+static UINT s_iRetVal;
+
+
+/********************************************************************
+ WcaGlobalInitialize() - initializes the Wca library, should be
+ called once per custom action Dll during
+ DllMain on DLL_PROCESS_ATTACH
+
+********************************************************************/
+extern "C" void WIXAPI WcaGlobalInitialize(
+ __in HINSTANCE hInst
+ )
+{
+ g_hInstCADLL = hInst;
+ MemInitialize();
+
+ AssertSetModule(g_hInstCADLL);
+ AssertSetDisplayFunction(WcaDisplayAssert);
+}
+
+
+/********************************************************************
+ WcaGlobalFinalize() - finalizes the Wca library, should be the
+ called once per custom action Dll during
+ DllMain on DLL_PROCESS_DETACH
+
+********************************************************************/
+extern "C" void WIXAPI WcaGlobalFinalize()
+{
+#ifdef DEBUG
+ if (WcaIsInitialized())
+ {
+ CHAR szBuf[2048];
+ StringCchPrintfA(szBuf, countof(szBuf), "CustomAction %s called WcaInitialize() but not WcaFinalize()", WcaGetLogName());
+
+ AssertSz(FALSE, szBuf);
+ }
+#endif
+ MemUninitialize();
+ g_hInstCADLL = NULL;
+}
+
+
+/********************************************************************
+ WcaInitialize() - initializes the Wca framework, should be the first
+ thing called by all CustomActions
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaInitialize(
+ __in MSIHANDLE hInstall,
+ __in_z PCSTR szCustomActionLogName
+ )
+{
+ WCHAR wzCAFileName[MAX_PATH] = {0};
+ DWORD dwMajorVersion = 0;
+ DWORD dwMinorVersion = 0;
+
+ // these statics should be called once per CustomAction invocation.
+ // Darwin does doesn't preserve DLL state across CustomAction calls so
+ // these should always be initialized to NULL. If that behavior changes
+ // we would need to do a careful review of all of our module/global data.
+ AssertSz(!s_fInitialized, "WcaInitialize() should only be called once per CustomAction");
+ Assert(NULL == s_hInstall);
+ Assert(NULL == s_hDatabase);
+ Assert(0 == *s_szCustomActionLogName);
+
+ HRESULT hr = S_OK;
+
+ s_fInitialized = TRUE;
+ s_iRetVal = ERROR_SUCCESS; // assume all will go well
+
+ s_hInstall = hInstall;
+ s_hDatabase = ::MsiGetActiveDatabase(s_hInstall); // may return null if deferred CustomAction
+
+ hr = ::StringCchCopy(s_szCustomActionLogName, countof(s_szCustomActionLogName), szCustomActionLogName);
+ ExitOnFailure(hr, "Failed to copy CustomAction log name: %s", szCustomActionLogName);
+
+ // If we got the database handle IE: immediate CA
+ if (s_hDatabase)
+ {
+ hr = SetVerboseLoggingAtom(IsVerboseLogging());
+ ExitOnFailure(hr, "Failed to set verbose logging global atom");
+ }
+
+ if (!::GetModuleFileNameW(g_hInstCADLL, wzCAFileName, countof(wzCAFileName)))
+ {
+ ExitWithLastError(hr, "Failed to get module filename");
+ }
+
+ FileVersion(wzCAFileName, &dwMajorVersion, &dwMinorVersion); // Ignore failure, just log 0.0.0.0
+
+ WcaLog(LOGMSG_VERBOSE, "Entering %s in %ls, version %u.%u.%u.%u", szCustomActionLogName, wzCAFileName, (DWORD)HIWORD(dwMajorVersion), (DWORD)LOWORD(dwMajorVersion), (DWORD)HIWORD(dwMinorVersion), (DWORD)LOWORD(dwMinorVersion));
+
+ Assert(s_hInstall);
+LExit:
+ if (FAILED(hr))
+ {
+ if (s_hDatabase)
+ {
+ ::MsiCloseHandle(s_hDatabase);
+ s_hDatabase = NULL;
+ }
+
+ s_hInstall = NULL;
+ s_fInitialized = FALSE;
+ }
+
+ return hr;
+}
+
+
+/********************************************************************
+ WcaFinalize() - cleans up after the Wca framework, should be the last
+ thing called by all CustomActions
+
+********************************************************************/
+extern "C" UINT WIXAPI WcaFinalize(
+ __in UINT iReturnValue
+ )
+{
+ AssertSz(!WcaIsWow64Initialized(), "WcaFinalizeWow64() should be called before calling WcaFinalize()");
+
+ // clean up after our initialization
+ if (s_hDatabase)
+ {
+ ::MsiCloseHandle(s_hDatabase);
+ s_hDatabase = NULL;
+ }
+
+ s_hInstall = NULL;
+ s_fInitialized = FALSE;
+
+ // if no error occurred during the processing of the CusotmAction return the passed in return value
+ // otherwise return the previous failure
+ return (ERROR_SUCCESS == s_iRetVal) ? iReturnValue : s_iRetVal;
+}
+
+
+/********************************************************************
+ WcaIsInitialized() - determines if WcaInitialize() has been called
+
+********************************************************************/
+extern "C" BOOL WIXAPI WcaIsInitialized()
+{
+ return s_fInitialized;
+}
+
+
+/********************************************************************
+ WcaGetInstallHandle() - gets the handle to the active install session
+
+********************************************************************/
+extern "C" MSIHANDLE WIXAPI WcaGetInstallHandle()
+{
+ AssertSz(s_hInstall, "WcaInitialize() should be called before attempting to access the install handle.");
+ return s_hInstall;
+}
+
+
+/********************************************************************
+ WcaGetDatabaseHandle() - gets the handle to the active database
+
+ NOTE: this function can only be used in immediate CustomActions.
+ Deferred CustomActions do not have access to the active
+ database.
+********************************************************************/
+extern "C" MSIHANDLE WIXAPI WcaGetDatabaseHandle()
+{
+ AssertSz(s_hDatabase, "WcaInitialize() should be called before attempting to access the install handle. Also note that deferred CustomActions do not have access to the active database.");
+ return s_hDatabase;
+}
+
+
+/********************************************************************
+ WcaGetLogName() - gets the name of the CustomAction used in logging
+
+********************************************************************/
+extern "C" const char* WIXAPI WcaGetLogName()
+{
+ return s_szCustomActionLogName;
+}
+
+
+/********************************************************************
+ WcaSetReturnValue() - sets the value to return from the CustomAction
+
+********************************************************************/
+extern "C" void WIXAPI WcaSetReturnValue(
+ __in UINT iReturnValue
+ )
+{
+ s_iRetVal = iReturnValue;
+}
+
+
+/********************************************************************
+ WcaCancelDetected() - determines if the user has canceled yet
+
+ NOTE: returns true when WcaSetReturnValue() is set to ERROR_INSTALL_USEREXIT
+********************************************************************/
+extern "C" BOOL WIXAPI WcaCancelDetected()
+{
+ return ERROR_INSTALL_USEREXIT == s_iRetVal;
+}
diff --git a/src/wcautil/wcautil.nuspec b/src/wcautil/wcautil.nuspec
new file mode 100644
index 0000000..3d1d272
--- /dev/null
+++ b/src/wcautil/wcautil.nuspec
@@ -0,0 +1,23 @@
+
+
+
+ $id$
+ $version$
+ $authors$
+ $authors$
+ https://github.com/wixtoolset/wcautil/blob/master/LICENSE.TXT
+ https://github.com/wixtoolset/wcautil
+ false
+ $description$
+ $copyright$
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/wcautil/wcautil.vcxproj b/src/wcautil/wcautil.vcxproj
new file mode 100644
index 0000000..61bd943
--- /dev/null
+++ b/src/wcautil/wcautil.vcxproj
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {5B3714B6-3A76-463E-8595-D48DA276C512}
+ StaticLibrary
+ wcautil
+ true
+ v141_xp
+ MultiByte
+ WiX Toolset Custom Action native utility library
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+
+
+
+
+
+
+
+
+ caerr.wxi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/wcautil/wcawow64.cpp b/src/wcautil/wcawow64.cpp
new file mode 100644
index 0000000..8174c43
--- /dev/null
+++ b/src/wcautil/wcawow64.cpp
@@ -0,0 +1,169 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+
+static HMODULE s_hKernel32;
+static BOOL s_fWow64Initialized;
+static BOOL (*s_pfnDisableWow64)(__out PVOID* );
+static BOOL (*s_pfnRevertWow64)(__in PVOID );
+static BOOL (*s_pfnIsWow64Process) (HANDLE, PBOOL);
+static PVOID s_Wow64FSRevertState;
+static BOOL s_fWow64FSDisabled;
+
+/********************************************************************
+ WcaInitializeWow64() - Initializes the Wow64 API
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaInitializeWow64()
+{
+ AssertSz(WcaIsInitialized(), "WcaInitialize() should be called before calling WcaInitializeWow64()");
+ AssertSz(!WcaIsWow64Initialized(), "WcaInitializeWow64() should not be called twice without calling WcaFinalizeWow64()");
+
+ s_fWow64Initialized = FALSE;
+ HRESULT hr = S_OK;
+ s_Wow64FSRevertState = NULL;
+ s_fWow64FSDisabled = false;
+
+ // Test if we have access to the Wow64 API, and store the result in bWow64APIPresent
+ s_hKernel32 = ::GetModuleHandleW(L"kernel32.dll");
+ if (!s_hKernel32)
+ {
+ ExitWithLastError(hr, "failed to get handle to kernel32.dll");
+ }
+
+ // This will test if we have access to the Wow64 API
+ s_pfnIsWow64Process = (BOOL (*)(HANDLE, PBOOL))::GetProcAddress(s_hKernel32, "IsWow64Process");
+ if (NULL != s_pfnIsWow64Process)
+ {
+ s_pfnDisableWow64 = (BOOL (*)(PVOID *))::GetProcAddress(s_hKernel32, "Wow64DisableWow64FsRedirection");
+ // If we fail, log the error but proceed, because we may not need a particular function, or the Wow64 API at all
+ if (!s_pfnDisableWow64)
+ {
+ return S_FALSE;
+ }
+
+ s_pfnRevertWow64 = (BOOL (*)(PVOID))::GetProcAddress(s_hKernel32, "Wow64RevertWow64FsRedirection");
+ if (!s_pfnRevertWow64)
+ {
+ return S_FALSE;
+ }
+
+ if (s_pfnDisableWow64 && s_pfnRevertWow64)
+ {
+ s_fWow64Initialized = TRUE;
+ }
+ }
+ else
+ {
+ return S_FALSE;
+ }
+
+LExit:
+
+ return hr;
+}
+
+/********************************************************************
+ WcaIsWow64Process() - determines if the current process is running
+ in WOW
+
+********************************************************************/
+extern "C" BOOL WIXAPI WcaIsWow64Process()
+{
+ BOOL fIsWow64Process = FALSE;
+ if (s_fWow64Initialized)
+ {
+ if (!s_pfnIsWow64Process(GetCurrentProcess(), &fIsWow64Process))
+ {
+ // clear out the value since call failed
+ fIsWow64Process = FALSE;
+ }
+ }
+ return fIsWow64Process;
+}
+
+/********************************************************************
+ WcaIsWow64Initialized() - determines if WcaInitializeWow64() has
+ been successfully called
+
+********************************************************************/
+extern "C" BOOL WIXAPI WcaIsWow64Initialized()
+{
+ return s_fWow64Initialized;
+}
+
+/********************************************************************
+ WcaDisableWow64FSRedirection() - Disables Wow64 FS Redirection
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaDisableWow64FSRedirection()
+{
+ AssertSz(s_fWow64Initialized && s_pfnDisableWow64 != NULL, "WcaDisableWow64FSRedirection() called, but Wow64 API was not initialized");
+
+#ifdef DEBUG
+ AssertSz(!s_fWow64FSDisabled, "You must call WcaRevertWow64FSRedirection() before calling WcaDisableWow64FSRedirection() again");
+#endif
+
+ HRESULT hr = S_OK;
+ if (s_pfnDisableWow64(&s_Wow64FSRevertState))
+ {
+ s_fWow64FSDisabled = TRUE;
+ }
+ else
+ {
+ ExitWithLastError(hr, "Failed to disable WOW64.");
+ }
+
+LExit:
+ return hr;
+}
+
+/********************************************************************
+ WcaRevertWow64FSRedirection() - Reverts Wow64 FS Redirection to its
+ pre-disabled state
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaRevertWow64FSRedirection()
+{
+ AssertSz(s_fWow64Initialized && s_pfnDisableWow64 != NULL, "WcaRevertWow64FSRedirection() called, but Wow64 API was not initialized");
+
+#ifdef DEBUG
+ AssertSz(s_fWow64FSDisabled, "You must call WcaDisableWow64FSRedirection() before calling WcaRevertWow64FSRedirection()");
+#endif
+
+ HRESULT hr = S_OK;
+ if (s_pfnRevertWow64(s_Wow64FSRevertState))
+ {
+ s_fWow64FSDisabled = FALSE;
+ }
+ else
+ {
+ ExitWithLastError(hr, "Failed to revert WOW64.");
+ }
+
+LExit:
+ return hr;
+}
+
+/********************************************************************
+ WcaFinalizeWow64() - Cleans up after Wow64 API Initialization
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaFinalizeWow64()
+{
+ if (s_fWow64FSDisabled)
+ {
+#ifdef DEBUG
+ AssertSz(FALSE, "WcaFinalizeWow64() called while Filesystem redirection was disabled.");
+#else
+ // If we aren't in debug mode, let's do our best to recover gracefully
+ WcaRevertWow64FSRedirection();
+#endif
+ }
+
+ s_fWow64Initialized = FALSE;
+ s_pfnDisableWow64 = NULL;
+ s_pfnRevertWow64 = NULL;
+
+ return S_OK;
+}
diff --git a/src/wcautil/wcawrap.cpp b/src/wcautil/wcawrap.cpp
new file mode 100644
index 0000000..625489c
--- /dev/null
+++ b/src/wcautil/wcawrap.cpp
@@ -0,0 +1,1643 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+
+
+/********************************************************************
+WcaProcessMessage() - sends a message from the CustomAction
+
+********************************************************************/
+extern "C" UINT WIXAPI WcaProcessMessage(
+ __in INSTALLMESSAGE eMessageType,
+ __in MSIHANDLE hRecord
+ )
+{
+ UINT er = ::MsiProcessMessage(WcaGetInstallHandle(), eMessageType, hRecord);
+ if (ERROR_INSTALL_USEREXIT == er || IDCANCEL == er)
+ {
+ WcaSetReturnValue(ERROR_INSTALL_USEREXIT);
+ }
+
+ return er;
+}
+
+
+/********************************************************************
+WcaErrorMessage() - sends an error message from the CustomAction using
+the Error table
+
+NOTE: Any and all var_args (...) must be WCHAR*
+ If you pass -1 to cArgs the count will be determined
+********************************************************************/
+extern "C" UINT __cdecl WcaErrorMessage(
+ __in int iError,
+ __in HRESULT hrError,
+ __in UINT uiType,
+ __in INT cArgs,
+ ...
+ )
+{
+ UINT er;
+ MSIHANDLE hRec = NULL;
+ va_list args = NULL;
+
+ uiType |= INSTALLMESSAGE_ERROR; // ensure error type is set
+ hRec = ::MsiCreateRecord(cArgs + 2);
+ if (!hRec)
+ {
+ er = ERROR_OUTOFMEMORY;
+ ExitOnFailure(HRESULT_FROM_WIN32(er), "failed to create record when sending error message");
+ }
+
+ er = ::MsiRecordSetInteger(hRec, 1, iError);
+ ExitOnFailure(HRESULT_FROM_WIN32(er), "failed to set error code into error message");
+
+ er = ::MsiRecordSetInteger(hRec, 2, hrError);
+ ExitOnFailure(HRESULT_FROM_WIN32(er), "failed to set hresult code into error message");
+
+ va_start(args, cArgs);
+ if (-1 == cArgs)
+ {
+ LPCWSTR wzArg = NULL;
+ va_list iter = args;
+ cArgs = 0;
+
+ while (NULL != (wzArg = va_arg(iter, WCHAR*)) && L'\0' != *wzArg)
+ {
+ ++cArgs;
+ }
+ }
+
+ for (INT i = 0; i < cArgs; i++)
+ {
+ er = ::MsiRecordSetStringW(hRec, i + 3, va_arg(args, WCHAR*));
+ ExitOnFailure(HRESULT_FROM_WIN32(er), "failed to set string string into error message");
+ }
+ va_end(args);
+
+ er = WcaProcessMessage(static_cast(uiType), hRec);
+LExit:
+ if (args)
+ {
+ va_end(args);
+ }
+
+ if (hRec)
+ {
+ ::MsiCloseHandle(hRec);
+ }
+
+ return er;
+}
+
+
+/********************************************************************
+WcaProgressMessage() - extends the progress bar or sends a progress
+update from the CustomAction
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaProgressMessage(
+ __in UINT uiCost,
+ __in BOOL fExtendProgressBar
+ )
+{
+ static BOOL fExplicitProgressMessages = FALSE;
+
+ HRESULT hr = S_OK;
+ UINT er = ERROR_SUCCESS;
+ MSIHANDLE hRec = ::MsiCreateRecord(3);
+
+ // if aren't extending the progress bar and we haven't switched into explicit message mode
+ if (!fExtendProgressBar && !fExplicitProgressMessages)
+ {
+ AssertSz(::MsiGetMode(WcaGetInstallHandle(), MSIRUNMODE_SCHEDULED) ||
+ ::MsiGetMode(WcaGetInstallHandle(), MSIRUNMODE_COMMIT) ||
+ ::MsiGetMode(WcaGetInstallHandle(), MSIRUNMODE_ROLLBACK), "can only send progress bar messages in a deferred CustomAction");
+
+ // tell Darwin to use explicit progress messages
+ ::MsiRecordSetInteger(hRec, 1, 1);
+ ::MsiRecordSetInteger(hRec, 2, 1);
+ ::MsiRecordSetInteger(hRec, 3, 0);
+
+ er = WcaProcessMessage(INSTALLMESSAGE_PROGRESS, hRec);
+ if (0 == er || IDOK == er || IDYES == er)
+ {
+ hr = S_OK;
+ }
+ else if (IDABORT == er || IDCANCEL == er)
+ {
+ WcaSetReturnValue(ERROR_INSTALL_USEREXIT); // note that the user said exit
+ ExitFunction1(hr = S_FALSE);
+ }
+ else
+ {
+ hr = E_UNEXPECTED;
+ }
+ ExitOnFailure(hr, "failed to tell Darwin to use explicit progress messages");
+
+ fExplicitProgressMessages = TRUE;
+ }
+#if DEBUG
+ else if (fExtendProgressBar) // if we are extending the progress bar, make sure we're not deferred
+ {
+ AssertSz(!::MsiGetMode(WcaGetInstallHandle(), MSIRUNMODE_SCHEDULED), "cannot add ticks to progress bar length from deferred CustomAction");
+ }
+#endif
+
+ // send the progress message
+ ::MsiRecordSetInteger(hRec, 1, (fExtendProgressBar) ? 3 : 2);
+ ::MsiRecordSetInteger(hRec, 2, uiCost);
+ ::MsiRecordSetInteger(hRec, 3, 0);
+
+ er = WcaProcessMessage(INSTALLMESSAGE_PROGRESS, hRec);
+ if (0 == er || IDOK == er || IDYES == er)
+ {
+ hr = S_OK;
+ }
+ else if (IDABORT == er || IDCANCEL == er)
+ {
+ WcaSetReturnValue(ERROR_INSTALL_USEREXIT); // note that the user said exit
+ hr = S_FALSE;
+ }
+ else
+ {
+ hr = E_UNEXPECTED;
+ }
+
+LExit:
+ if (hRec)
+ {
+ ::MsiCloseHandle(hRec);
+ }
+
+ return hr;
+}
+
+
+/********************************************************************
+WcaIsInstalling() - determines if a pair of installstates means install
+
+********************************************************************/
+extern "C" BOOL WIXAPI WcaIsInstalling(
+ __in INSTALLSTATE isInstalled,
+ __in INSTALLSTATE isAction
+ )
+{
+ return (INSTALLSTATE_LOCAL == isAction ||
+ INSTALLSTATE_SOURCE == isAction ||
+ (INSTALLSTATE_DEFAULT == isAction &&
+ (INSTALLSTATE_LOCAL == isInstalled ||
+ INSTALLSTATE_SOURCE == isInstalled)));
+}
+
+/********************************************************************
+WcaIsReInstalling() - determines if a pair of installstates means reinstall
+
+********************************************************************/
+extern "C" BOOL WIXAPI WcaIsReInstalling(
+ __in INSTALLSTATE isInstalled,
+ __in INSTALLSTATE isAction
+ )
+{
+ return ((INSTALLSTATE_LOCAL == isAction ||
+ INSTALLSTATE_SOURCE == isAction ||
+ INSTALLSTATE_DEFAULT == isAction) &&
+ (INSTALLSTATE_LOCAL == isInstalled ||
+ INSTALLSTATE_SOURCE == isInstalled));
+}
+
+
+/********************************************************************
+WcaIsUninstalling() - determines if a pair of installstates means uninstall
+
+********************************************************************/
+extern "C" BOOL WIXAPI WcaIsUninstalling(
+ __in INSTALLSTATE isInstalled,
+ __in INSTALLSTATE isAction
+ )
+{
+ return ((INSTALLSTATE_ABSENT == isAction ||
+ INSTALLSTATE_REMOVED == isAction) &&
+ (INSTALLSTATE_LOCAL == isInstalled ||
+ INSTALLSTATE_SOURCE == isInstalled));
+}
+
+
+/********************************************************************
+WcaGetComponentToDo() - gets a component's install states and
+determines if they mean install, uninstall, or reinstall.
+********************************************************************/
+extern "C" WCA_TODO WIXAPI WcaGetComponentToDo(
+ __in_z LPCWSTR wzComponentId
+ )
+{
+ INSTALLSTATE isInstalled = INSTALLSTATE_UNKNOWN;
+ INSTALLSTATE isAction = INSTALLSTATE_UNKNOWN;
+ if (ERROR_SUCCESS != ::MsiGetComponentStateW(WcaGetInstallHandle(), wzComponentId, &isInstalled, &isAction))
+ {
+ return WCA_TODO_UNKNOWN;
+ }
+
+ if (WcaIsReInstalling(isInstalled, isAction))
+ {
+ return WCA_TODO_REINSTALL;
+ }
+ else if (WcaIsUninstalling(isInstalled, isAction))
+ {
+ return WCA_TODO_UNINSTALL;
+ }
+ else if (WcaIsInstalling(isInstalled, isAction))
+ {
+ return WCA_TODO_INSTALL;
+ }
+ else
+ {
+ return WCA_TODO_UNKNOWN;
+ }
+}
+
+
+/********************************************************************
+WcaSetComponentState() - sets the install state of a Component
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaSetComponentState(
+ __in_z LPCWSTR wzComponent,
+ __in INSTALLSTATE isState
+ )
+{
+ UINT er = ::MsiSetComponentStateW(WcaGetInstallHandle(), wzComponent, isState);
+ if (ERROR_INSTALL_USEREXIT == er)
+ {
+ WcaSetReturnValue(er);
+ }
+
+ return HRESULT_FROM_WIN32(er);
+}
+
+
+/********************************************************************
+WcaTableExists() - determines if installing database contains a table
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaTableExists(
+ __in_z LPCWSTR wzTable
+ )
+{
+ HRESULT hr = S_OK;
+ UINT er = ERROR_SUCCESS;
+
+ // NOTE: The following line of commented out code should work in a
+ // CustomAction but does not in Windows Installer v1.1
+ // er = ::MsiDatabaseIsTablePersistentW(hDatabase, wzTable);
+
+ // a "most elegant" workaround a Darwin v1.1 bug
+ PMSIHANDLE hRec;
+ er = ::MsiDatabaseGetPrimaryKeysW(WcaGetDatabaseHandle(), wzTable, &hRec);
+
+ if (ERROR_SUCCESS == er)
+ {
+ hr = S_OK;
+ }
+ else if (ERROR_INVALID_TABLE == er)
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ hr = E_FAIL;
+ }
+ Assert(SUCCEEDED(hr));
+
+ return hr;
+}
+
+
+/********************************************************************
+WcaOpenView() - opens a view on the installing database
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaOpenView(
+ __in_z LPCWSTR wzSql,
+ __out MSIHANDLE* phView
+ )
+{
+ if (!wzSql || !*wzSql|| !phView)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = S_OK;
+ UINT er = ::MsiDatabaseOpenViewW(WcaGetDatabaseHandle(), wzSql, phView);
+ ExitOnWin32Error(er, hr, "failed to open view on database with SQL: %ls", wzSql);
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaExecuteView() - executes a parameterized open view on the installing database
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaExecuteView(
+ __in MSIHANDLE hView,
+ __in MSIHANDLE hRec
+ )
+{
+ if (!hView)
+ {
+ return E_INVALIDARG;
+ }
+ AssertSz(hRec, "Use WcaOpenExecuteView() if you don't need to pass in a record");
+
+ HRESULT hr = S_OK;
+ UINT er = ::MsiViewExecute(hView, hRec);
+ ExitOnWin32Error(er, hr, "failed to execute view");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaOpenExecuteView() - opens and executes a view on the installing database
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaOpenExecuteView(
+ __in_z LPCWSTR wzSql,
+ __out MSIHANDLE* phView
+ )
+{
+ if (!wzSql || !*wzSql|| !phView)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = S_OK;
+ UINT er = ::MsiDatabaseOpenViewW(WcaGetDatabaseHandle(), wzSql, phView);
+ ExitOnWin32Error(er, hr, "failed to open view on database");
+
+ er = ::MsiViewExecute(*phView, NULL);
+ ExitOnWin32Error(er, hr, "failed to execute view");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaFetchRecord() - gets the next record from a view on the installing database
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaFetchRecord(
+ __in MSIHANDLE hView,
+ __out MSIHANDLE* phRec
+ )
+{
+ if (!hView|| !phRec)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = S_OK;
+ UINT er = ::MsiViewFetch(hView, phRec);
+ hr = HRESULT_FROM_WIN32(er);
+ if (FAILED(hr) && E_NOMOREITEMS != hr)
+ {
+ ExitOnFailure(hr, "failed to fetch record from view");
+ }
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaFetchSingleRecord() - gets a single record from a view on the installing database
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaFetchSingleRecord(
+ __in MSIHANDLE hView,
+ __out MSIHANDLE* phRec
+ )
+{
+ if (!hView|| !phRec)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = S_OK;
+ UINT er = ::MsiViewFetch(hView, phRec);
+ if (ERROR_NO_MORE_ITEMS == er)
+ {
+ hr = S_FALSE;
+ }
+ else
+ {
+ hr = HRESULT_FROM_WIN32(er);
+ }
+ ExitOnFailure(hr, "failed to fetch single record from view");
+
+#ifdef DEBUG // only do this in debug to verify that a single record was returned
+ MSIHANDLE hRecTest;
+ er = ::MsiViewFetch(hView, &hRecTest);
+ AssertSz(ERROR_NO_MORE_ITEMS == er && NULL == hRecTest, "WcaSingleFetch() did not fetch a single record");
+ ::MsiCloseHandle(hRecTest);
+#endif
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaGetProperty - gets a string property value from the active install
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetProperty(
+ __in_z LPCWSTR wzProperty,
+ __inout LPWSTR* ppwzData
+ )
+{
+ if (!wzProperty || !*wzProperty || !ppwzData)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = S_OK;
+ UINT er = ERROR_SUCCESS;
+ DWORD_PTR cch = 0;
+
+ if (!*ppwzData)
+ {
+ WCHAR szEmpty[1] = L"";
+ er = ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, szEmpty, (DWORD *)&cch);
+ if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er)
+ {
+ hr = StrAlloc(ppwzData, ++cch);
+ }
+ else
+ {
+ hr = HRESULT_FROM_WIN32(er);
+ }
+ ExitOnFailure(hr, "Failed to allocate string for Property '%ls'", wzProperty);
+ }
+ else
+ {
+ hr = StrMaxLength(*ppwzData, &cch);
+ ExitOnFailure(hr, "Failed to get previous size of property data string.");
+ }
+
+ er = ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, *ppwzData, (DWORD *)&cch);
+ if (ERROR_MORE_DATA == er)
+ {
+ Assert(*ppwzData);
+ hr = StrAlloc(ppwzData, ++cch);
+ ExitOnFailure(hr, "Failed to allocate string for Property '%ls'", wzProperty);
+
+ er = ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, *ppwzData, (DWORD *)&cch);
+ }
+ ExitOnWin32Error(er, hr, "Failed to get data for property '%ls'", wzProperty);
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaGetFormattedProperty - gets a formatted string property value from
+the active install
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetFormattedProperty(
+ __in_z LPCWSTR wzProperty,
+ __out LPWSTR* ppwzData
+ )
+{
+ if (!wzProperty || !*wzProperty || !ppwzData)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = S_OK;
+ LPWSTR pwzPropertyValue = NULL;
+
+ hr = WcaGetProperty(wzProperty, &pwzPropertyValue);
+ ExitOnFailure(hr, "failed to get %ls", wzProperty);
+
+ hr = WcaGetFormattedString(pwzPropertyValue, ppwzData);
+ ExitOnFailure(hr, "failed to get formatted value for property: '%ls' with value: '%ls'", wzProperty, pwzPropertyValue);
+
+LExit:
+ ReleaseStr(pwzPropertyValue);
+
+ return hr;
+}
+
+
+/********************************************************************
+WcaGetFormattedString - gets a formatted string value from
+the active install
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetFormattedString(
+ __in_z LPCWSTR wzString,
+ __out LPWSTR* ppwzData
+ )
+{
+ if (!wzString || !*wzString || !ppwzData)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = S_OK;
+ UINT er = ERROR_SUCCESS;
+ PMSIHANDLE hRecord = ::MsiCreateRecord(1);
+ DWORD_PTR cch = 0;
+
+ er = ::MsiRecordSetStringW(hRecord, 0, wzString);
+ ExitOnWin32Error(er, hr, "Failed to set record field 0 with '%ls'", wzString);
+
+ if (!*ppwzData)
+ {
+ WCHAR szEmpty[1] = L"";
+ er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecord, szEmpty, (DWORD *)&cch);
+ if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er)
+ {
+ hr = StrAlloc(ppwzData, ++cch);
+ }
+ else
+ {
+ hr = HRESULT_FROM_WIN32(er);
+ }
+ ExitOnFailure(hr, "Failed to allocate string for formatted string: '%ls'", wzString);
+ }
+ else
+ {
+ hr = StrMaxLength(*ppwzData, &cch);
+ ExitOnFailure(hr, "Failed to get previous size of property data string");
+ }
+
+ er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecord, *ppwzData, (DWORD *)&cch);
+ if (ERROR_MORE_DATA == er)
+ {
+ hr = StrAlloc(ppwzData, ++cch);
+ ExitOnFailure(hr, "Failed to allocate string for formatted string: '%ls'", wzString);
+
+ er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecord, *ppwzData, (DWORD *)&cch);
+ }
+ ExitOnWin32Error(er, hr, "Failed to get formatted string: '%ls'", wzString);
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaGetIntProperty - gets an integer property value from the active install
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetIntProperty(
+ __in_z LPCWSTR wzProperty,
+ __inout int* piData
+ )
+{
+ if (!piData)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ UINT er;
+
+ WCHAR wzValue[32];
+ DWORD cch = countof(wzValue) - 1;
+
+ er = ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, wzValue, &cch);
+ ExitOnWin32Error(er, hr, "Failed to get data for property '%ls'", wzProperty);
+
+ *piData = wcstol(wzValue, NULL, 10);
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaGetTargetPath - gets the target path for a specified folder
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetTargetPath(
+ __in_z LPCWSTR wzFolder,
+ __out LPWSTR* ppwzData
+ )
+{
+ if (!wzFolder || !*wzFolder || !ppwzData)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+
+ UINT er = ERROR_SUCCESS;
+ DWORD_PTR cch = 0;
+
+ if (!*ppwzData)
+ {
+ WCHAR szEmpty[1] = L"";
+ er = ::MsiGetTargetPathW(WcaGetInstallHandle(), wzFolder, szEmpty, (DWORD*)&cch);
+ if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er)
+ {
+ ++cch; //Add one for the null terminator
+ hr = StrAlloc(ppwzData, cch);
+ }
+ else
+ {
+ hr = HRESULT_FROM_WIN32(er);
+ }
+ ExitOnFailure(hr, "Failed to allocate string for target path of folder: '%ls'", wzFolder);
+ }
+ else
+ {
+ hr = StrMaxLength(*ppwzData, &cch);
+ ExitOnFailure(hr, "Failed to get previous size of string");
+ }
+
+ er = ::MsiGetTargetPathW(WcaGetInstallHandle(), wzFolder, *ppwzData, (DWORD*)&cch);
+ if (ERROR_MORE_DATA == er)
+ {
+ ++cch;
+ hr = StrAlloc(ppwzData, cch);
+ ExitOnFailure(hr, "Failed to allocate string for target path of folder: '%ls'", wzFolder);
+
+ er = ::MsiGetTargetPathW(WcaGetInstallHandle(), wzFolder, *ppwzData, (DWORD*)&cch);
+ }
+ ExitOnWin32Error(er, hr, "Failed to get target path for folder '%ls'", wzFolder);
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaSetProperty - sets a string property value in the active install
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaSetProperty(
+ __in_z LPCWSTR wzPropertyName,
+ __in_z LPCWSTR wzPropertyValue
+ )
+{
+ HRESULT hr = S_OK;
+
+ if (!wzPropertyName || !*wzPropertyName || !wzPropertyValue)
+ return E_INVALIDARG;
+
+ UINT er = ::MsiSetPropertyW(WcaGetInstallHandle(), wzPropertyName, wzPropertyValue);
+ ExitOnWin32Error(er, hr, "failed to set property: %ls", wzPropertyName);
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaSetIntProperty - sets a integer property value in the active install
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaSetIntProperty(
+ __in_z LPCWSTR wzPropertyName,
+ __in int nPropertyValue
+ )
+{
+ if (!wzPropertyName || !*wzPropertyName)
+ return E_INVALIDARG;
+
+ // 12 characters should be enough for a 32-bit int: 10 digits, 1 sign, 1 null
+ WCHAR wzPropertyValue[13];
+ HRESULT hr = StringCchPrintfW(wzPropertyValue, countof(wzPropertyValue), L"%d", nPropertyValue);
+ ExitOnFailure(hr, "failed to convert into string property value: %d", nPropertyValue);
+
+ UINT er = ::MsiSetPropertyW(WcaGetInstallHandle(), wzPropertyName, wzPropertyValue);
+ ExitOnWin32Error(er, hr, "failed to set property: %ls", wzPropertyName);
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaIsPropertySet() - returns TRUE if property is set
+
+********************************************************************/
+extern "C" BOOL WIXAPI WcaIsPropertySet(
+ __in LPCSTR szProperty
+ )
+{
+ DWORD cchProperty = 0;
+ char szEmpty[1] = "";
+#ifdef DEBUG
+ UINT er =
+#endif
+ ::MsiGetPropertyA(WcaGetInstallHandle(), szProperty, szEmpty, &cchProperty);
+ AssertSz(ERROR_INVALID_PARAMETER != er && ERROR_INVALID_HANDLE != er, "Unexpected return value from ::MsiGetProperty()");
+
+ return 0 < cchProperty; // property is set if the length is greater than zero
+}
+
+
+/********************************************************************
+WcaIsUnicodePropertySet() - returns TRUE if property is set
+
+********************************************************************/
+extern "C" BOOL WIXAPI WcaIsUnicodePropertySet(
+ __in LPCWSTR wzProperty
+ )
+{
+ DWORD cchProperty = 0;
+ wchar_t wzEmpty[1] = L"";
+#ifdef DEBUG
+ UINT er =
+#endif
+ ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, wzEmpty, &cchProperty);
+ AssertSz(ERROR_INVALID_PARAMETER != er && ERROR_INVALID_HANDLE != er, "Unexpected return value from ::MsiGetProperty()");
+
+ return 0 < cchProperty; // property is set if the length is greater than zero
+}
+
+
+/********************************************************************
+WcaGetRecordInteger() - gets an integer field out of a record
+
+NOTE: returns S_FALSE if the field was null
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetRecordInteger(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __inout int* piData
+ )
+{
+ if (!hRec || !piData)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ *piData = ::MsiRecordGetInteger(hRec, uiField);
+ if (MSI_NULL_INTEGER == *piData)
+ hr = S_FALSE;
+
+ //LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaGetRecordString() - gets a string field out of a record
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetRecordString(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __inout LPWSTR* ppwzData
+ )
+{
+ if (!hRec || !ppwzData)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ UINT er;
+ DWORD_PTR cch = 0;
+
+ if (!*ppwzData)
+ {
+ WCHAR szEmpty[1] = L"";
+ er = ::MsiRecordGetStringW(hRec, uiField, szEmpty, (DWORD*)&cch);
+ if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er)
+ {
+ hr = StrAlloc(ppwzData, ++cch);
+ }
+ else
+ {
+ hr = HRESULT_FROM_WIN32(er);
+ }
+ ExitOnFailure(hr, "Failed to allocate memory for record string");
+ }
+ else
+ {
+ hr = StrMaxLength(*ppwzData, &cch);
+ ExitOnFailure(hr, "Failed to get previous size of string");
+ }
+
+ er = ::MsiRecordGetStringW(hRec, uiField, *ppwzData, (DWORD*)&cch);
+ if (ERROR_MORE_DATA == er)
+ {
+ hr = StrAlloc(ppwzData, ++cch);
+ ExitOnFailure(hr, "Failed to allocate memory for record string");
+
+ er = ::MsiRecordGetStringW(hRec, uiField, *ppwzData, (DWORD*)&cch);
+ }
+ ExitOnWin32Error(er, hr, "Failed to get string from record");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+HideNulls() - internal helper function to escape [~] in formatted strings
+
+********************************************************************/
+static void HideNulls(
+ __inout_z LPWSTR wzData
+ )
+{
+ LPWSTR pwz = wzData;
+
+ while(*pwz)
+ {
+ if (pwz[0] == L'[' && pwz[1] == L'~' && pwz[2] == L']') // found a null [~]
+ {
+ pwz[0] = L'!'; // turn it into !$!
+ pwz[1] = L'$';
+ pwz[2] = L'!';
+ pwz += 3;
+ }
+ else
+ {
+ ++pwz;
+ }
+ }
+}
+
+
+/********************************************************************
+RevealNulls() - internal helper function to unescape !$! in formatted strings
+
+********************************************************************/
+static void RevealNulls(
+ __inout_z LPWSTR wzData
+ )
+{
+ LPWSTR pwz = wzData;
+
+ while(*pwz)
+ {
+ if (pwz[0] == L'!' && pwz[1] == L'$' && pwz[2] == L'!') // found the fake null !$!
+ {
+ pwz[0] = L'['; // turn it back into [~]
+ pwz[1] = L'~';
+ pwz[2] = L']';
+ pwz += 3;
+ }
+ else
+ {
+ ++pwz;
+ }
+ }
+}
+
+
+/********************************************************************
+WcaGetRecordFormattedString() - gets formatted string filed from record
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetRecordFormattedString(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __inout LPWSTR* ppwzData
+ )
+{
+ if (!hRec || !ppwzData)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = S_OK;
+ UINT er;
+ DWORD_PTR cch = 0;
+ PMSIHANDLE hRecFormat;
+
+ // get the format string
+ hr = WcaGetRecordString(hRec, uiField, ppwzData);
+ ExitOnFailure(hr, "failed to get string from record");
+
+ if (!**ppwzData)
+ {
+ ExitFunction();
+ }
+
+ // hide the nulls '[~]' so we can get them back after formatting
+ HideNulls(*ppwzData);
+
+ // set up the format record
+ hRecFormat = ::MsiCreateRecord(1);
+ ExitOnNull(hRecFormat, hr, E_UNEXPECTED, "Failed to create record to format string");
+ hr = WcaSetRecordString(hRecFormat, 0, *ppwzData);
+ ExitOnFailure(hr, "failed to set string to format record");
+
+ // format the string
+ hr = StrMaxLength(*ppwzData, &cch);
+ ExitOnFailure(hr, "failed to get max length of string");
+
+ er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecFormat, *ppwzData, (DWORD*)&cch);
+ if (ERROR_MORE_DATA == er)
+ {
+ hr = StrAlloc(ppwzData, ++cch);
+ ExitOnFailure(hr, "Failed to allocate memory for record string");
+
+ er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecFormat, *ppwzData, (DWORD*)&cch);
+ }
+ ExitOnWin32Error(er, hr, "Failed to format string");
+
+ // put the nulls back
+ RevealNulls(*ppwzData);
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaGetRecordFormattedInteger() - gets formatted integer from record
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetRecordFormattedInteger(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __out int* piData
+ )
+{
+ if (!hRec || !piData)
+ {
+ return E_INVALIDARG;
+ }
+
+ HRESULT hr = S_OK;
+ LPWSTR pwzData = NULL;
+
+ hr = WcaGetRecordFormattedString(hRec, uiField, &pwzData);
+ ExitOnFailure(hr, "failed to get record field: %u", uiField);
+ if (pwzData && *pwzData)
+ {
+ LPWSTR wz = NULL;
+ *piData = wcstol(pwzData, &wz, 10);
+ if (wz && *wz)
+ {
+ hr = E_INVALIDARG;
+ ExitOnFailure(hr, "failed to parse record field: %u as number: %ls", uiField, pwzData);
+ }
+ }
+ else
+ {
+ *piData = MSI_NULL_INTEGER;
+ }
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaAllocStream() - creates a byte stream of the specified size
+
+NOTE: Use WcaFreeStream() to release the byte stream
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaAllocStream(
+ __deref_out_bcount_part(cbData, 0) BYTE** ppbData,
+ __in DWORD cbData
+ )
+{
+ Assert(ppbData);
+ HRESULT hr;
+ BYTE* pbNewData;
+
+ if (*ppbData)
+ pbNewData = static_cast(MemReAlloc(*ppbData, cbData, TRUE));
+ else
+ pbNewData = static_cast(MemAlloc(cbData, TRUE));
+
+ if (!pbNewData)
+ {
+ ExitOnLastError(hr, "Failed to allocate string");
+ }
+
+ *ppbData = pbNewData;
+ pbNewData = NULL;
+
+ hr = S_OK;
+LExit:
+ ReleaseMem(pbNewData);
+
+ return hr;
+}
+
+
+/********************************************************************
+WcaFreeStream() - frees a byte stream
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaFreeStream(
+ __in BYTE* pbData
+ )
+{
+ if (!pbData)
+ return E_INVALIDARG;
+
+ HRESULT hr = MemFree(pbData);
+ return hr;
+}
+
+
+/********************************************************************
+WcaGetRecordStream() - gets a byte stream field from record
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaGetRecordStream(
+ __in MSIHANDLE hRecBinary,
+ __in UINT uiField,
+ __deref_out_bcount_full(*pcbData) BYTE** ppbData,
+ __out DWORD* pcbData
+ )
+{
+ HRESULT hr = S_OK;
+ UINT er = ERROR_SUCCESS;
+
+ if (!hRecBinary || !ppbData || !pcbData)
+ return E_INVALIDARG;
+
+ *pcbData = 0;
+ er = ::MsiRecordReadStream(hRecBinary, uiField, NULL, pcbData);
+ ExitOnWin32Error(er, hr, "failed to get size of stream");
+
+ hr = WcaAllocStream(ppbData, *pcbData);
+ ExitOnFailure(hr, "failed to allocate data for stream");
+
+ er = ::MsiRecordReadStream(hRecBinary, uiField, (char*)*ppbData, pcbData);
+ ExitOnWin32Error(er, hr, "failed to read from stream");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaSetRecordString() - set a string field in record
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaSetRecordString(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __in_z LPCWSTR wzData
+ )
+{
+ if (!hRec || !wzData)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ UINT er = ::MsiRecordSetStringW(hRec, uiField, wzData);
+ ExitOnWin32Error(er, hr, "failed to set string in record");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaSetRecordInteger() - set a integer field in record
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaSetRecordInteger(
+ __in MSIHANDLE hRec,
+ __in UINT uiField,
+ __in int iValue
+ )
+{
+ if (!hRec)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ UINT er = ::MsiRecordSetInteger(hRec, uiField, iValue);
+ ExitOnWin32Error(er, hr, "failed to set integer in record");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+
+WcaDoDeferredAction() - schedules an action at this point in the script
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaDoDeferredAction(
+ __in_z LPCWSTR wzAction,
+ __in_z LPCWSTR wzCustomActionData,
+ __in UINT uiCost
+ )
+{
+ HRESULT hr = S_OK;
+ UINT er;
+
+ if (wzCustomActionData && *wzCustomActionData)
+ {
+ er = ::MsiSetPropertyW(WcaGetInstallHandle(), wzAction, wzCustomActionData);
+ ExitOnWin32Error(er, hr, "Failed to set CustomActionData for deferred action");
+ }
+
+ if (0 < uiCost)
+ {
+ hr = WcaProgressMessage(uiCost, TRUE); // add ticks to the progress bar
+ // TODO: handle the return codes correctly
+ }
+
+ er = ::MsiDoActionW(WcaGetInstallHandle(), wzAction);
+ if (ERROR_INSTALL_USEREXIT == er)
+ {
+ WcaSetReturnValue(er);
+ }
+ ExitOnWin32Error(er, hr, "Failed MsiDoAction on deferred action");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaCountOfCustomActionDataRecords() - counts the number of records
+passed to a deferred CustomAction
+
+********************************************************************/
+extern "C" DWORD WIXAPI WcaCountOfCustomActionDataRecords(
+ __in_z LPCWSTR wzData
+ )
+{
+ WCHAR delim[] = {MAGIC_MULTISZ_DELIM, 0}; // magic char followed by NULL terminator
+ DWORD dwCount = 0;
+
+ // Loop through until there are no delimiters, we are at the end of the string, or the delimiter is the last character in the string
+ for (LPCWSTR pwzCurrent = wzData; pwzCurrent && *pwzCurrent && *(pwzCurrent + 1); pwzCurrent = wcsstr(pwzCurrent, delim))
+ {
+ ++dwCount;
+ ++pwzCurrent;
+ }
+
+ return dwCount;
+}
+
+
+/********************************************************************
+BreakDownCustomActionData() - internal helper to chop up CustomActionData
+
+NOTE: this modifies the passed in data
+********************************************************************/
+static LPWSTR BreakDownCustomActionData(
+ __inout LPWSTR* ppwzData
+ )
+{
+ if (!ppwzData)
+ return NULL;
+ if (0 == *ppwzData)
+ return NULL;
+
+ WCHAR delim[] = {MAGIC_MULTISZ_DELIM, 0}; // magic char followed by Null terminator
+
+ LPWSTR pwzReturn = *ppwzData;
+ LPWSTR pwz = wcsstr(pwzReturn, delim);
+ if (pwz)
+ {
+ *pwz = 0;
+ *ppwzData = pwz + 1;
+ }
+ else
+ *ppwzData = 0;
+
+ return pwzReturn;
+}
+
+
+/********************************************************************
+RevertCustomActionData() - Reverts custom action data changes made
+ by BreakDownCustomActionData;
+
+NOTE: this modifies the passed in data
+********************************************************************/
+extern "C" void WIXAPI RevertCustomActionData(
+ __in LPWSTR wzRevertTo,
+ __in LPCWSTR wzRevertFrom
+ )
+{
+ if (!wzRevertTo)
+ return;
+ if (!wzRevertFrom)
+ return;
+ // start at the revert point and replace all \0 with MAGIC_MULTISZ_DELIM
+ for(LPWSTR wzIndex = wzRevertTo; wzIndex < wzRevertFrom; wzIndex++)
+ {
+ if (0 == *wzIndex)
+ {
+ *wzIndex = MAGIC_MULTISZ_DELIM;
+ }
+ }
+ return;
+}
+
+/********************************************************************
+WcaReadStringFromCaData() - reads a string out of the CustomActionData
+
+NOTE: this modifies the passed in ppwzCustomActionData variable
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaReadStringFromCaData(
+ __deref_in LPWSTR* ppwzCustomActionData,
+ __deref_out_z LPWSTR* ppwzString
+ )
+{
+ HRESULT hr = S_OK;
+
+ LPCWSTR pwz = BreakDownCustomActionData(ppwzCustomActionData);
+ if (!pwz)
+ return E_NOMOREITEMS;
+
+ hr = StrAllocString(ppwzString, pwz, 0);
+ ExitOnFailure(hr, "failed to allocate memory for string");
+
+ hr = S_OK;
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaReadIntegerFromCaData() - reads an integer out of the CustomActionData
+
+NOTE: this modifies the passed in ppwzCustomActionData variable
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaReadIntegerFromCaData(
+ __deref_in LPWSTR* ppwzCustomActionData,
+ __out int* piResult
+ )
+{
+ LPCWSTR pwz = BreakDownCustomActionData(ppwzCustomActionData);
+ if (!pwz || 0 == wcslen(pwz))
+ return E_NOMOREITEMS;
+
+ *piResult = wcstol(pwz, NULL, 10);
+ return S_OK;
+}
+
+
+/********************************************************************
+WcaReadStreamFromCaData() - reads a stream out of the CustomActionData
+
+NOTE: this modifies the passed in ppwzCustomActionData variable
+NOTE: returned stream should be freed with WcaFreeStream()
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaReadStreamFromCaData(
+ __deref_in LPWSTR* ppwzCustomActionData,
+ __deref_out_bcount(*pcbData) BYTE** ppbData,
+ __out DWORD_PTR* pcbData
+ )
+{
+ HRESULT hr;
+
+ LPCWSTR pwz = BreakDownCustomActionData(ppwzCustomActionData);
+ if (!pwz)
+ return E_NOMOREITEMS;
+
+ hr = StrAllocBase85Decode(pwz, ppbData, pcbData);
+ ExitOnFailure(hr, "failed to decode string into stream");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaWriteStringToCaData() - adds a string to the CustomActionData to
+feed a deferred CustomAction
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaWriteStringToCaData(
+ __in_z LPCWSTR wzString,
+ __deref_inout_z_opt LPWSTR* ppwzCustomActionData
+ )
+{
+ HRESULT hr = S_OK;
+ WCHAR delim[] = {MAGIC_MULTISZ_DELIM, 0}; // magic char followed by NULL terminator
+
+ if (!ppwzCustomActionData)
+ {
+ ExitFunction1(hr = E_INVALIDARG);
+ }
+
+ DWORD cchString = lstrlenW(wzString) + 1; // assume we'll be adding the delim
+ DWORD_PTR cchCustomActionData = 0;
+
+ if (*ppwzCustomActionData)
+ {
+ hr = StrMaxLength(*ppwzCustomActionData, &cchCustomActionData);
+ ExitOnFailure(hr, "failed to get length of custom action data");
+ }
+
+ if ((cchCustomActionData - lstrlenW(*ppwzCustomActionData)) < cchString + 1)
+ {
+ cchCustomActionData += cchString + 1 + 255; // add 255 for good measure
+ hr = StrAlloc(ppwzCustomActionData, cchCustomActionData);
+ ExitOnFailure(hr, "Failed to allocate memory for CustomActionData string");
+ }
+
+ if (**ppwzCustomActionData) // if data exists toss the delimiter on before adding more to the end
+ {
+ hr = ::StringCchCatW(*ppwzCustomActionData, cchCustomActionData, delim);
+ ExitOnFailure(hr, "Failed to concatenate CustomActionData string");
+ }
+
+ hr = ::StringCchCatW(*ppwzCustomActionData, cchCustomActionData, wzString);
+ ExitOnFailure(hr, "Failed to concatenate CustomActionData string");
+
+LExit:
+ return hr;
+}
+
+
+/********************************************************************
+WcaWriteIntegerToCaData() - adds an integer to the CustomActionData to
+feed a deferred CustomAction
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaWriteIntegerToCaData(
+ __in int i,
+ __deref_out_z_opt LPWSTR* ppwzCustomActionData
+ )
+{
+ WCHAR wzBuffer[13];
+ StringCchPrintfW(wzBuffer, countof(wzBuffer), L"%d", i);
+
+ return WcaWriteStringToCaData(wzBuffer, ppwzCustomActionData);
+}
+
+
+/********************************************************************
+WcaWriteStreamToCaData() - adds a byte stream to the CustomActionData to
+feed a deferred CustomAction
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaWriteStreamToCaData(
+ __in_bcount(cbData) const BYTE* pbData,
+ __in DWORD cbData,
+ __deref_inout_z_opt LPWSTR* ppwzCustomActionData
+ )
+{
+ HRESULT hr;
+ LPWSTR pwzData = NULL;
+
+ hr = StrAllocBase85Encode(pbData, cbData, &pwzData);
+ ExitOnFailure(hr, "failed to encode data into string");
+
+ hr = WcaWriteStringToCaData(pwzData, ppwzCustomActionData);
+
+LExit:
+ ReleaseStr(pwzData);
+ return hr;
+}
+
+
+/********************************************************************
+WcaAddTempRecord - adds a temporary record to the active database
+
+NOTE: you cannot use PMSIHANDLEs for the __in/__out parameters
+NOTE: uiUniquifyColumn can be 0 if no column needs to be made unique
+********************************************************************/
+extern "C" HRESULT __cdecl WcaAddTempRecord(
+ __inout MSIHANDLE* phTableView,
+ __inout MSIHANDLE* phColumns,
+ __in_z LPCWSTR wzTable,
+ __out_opt MSIDBERROR* pdbError,
+ __in UINT uiUniquifyColumn,
+ __in UINT cColumns,
+ ...
+ )
+{
+ Assert(phTableView && phColumns);
+
+ static DWORD dwUniquifyValue = ::GetTickCount();
+
+ HRESULT hr = S_OK;
+ UINT er = ERROR_SUCCESS;
+
+ LPWSTR pwzQuery = NULL;
+ PMSIHANDLE hTempRec;
+ DWORD i;
+ va_list args;
+
+ LPWSTR pwzData = NULL;
+ LPWSTR pwzUniquify = NULL;
+
+ //
+ // if we don't have a table and its columns already
+ //
+ if (NULL == *phTableView)
+ {
+ // set the query
+ hr = StrAllocFormatted(&pwzQuery, L"SELECT * FROM `%s`",wzTable);
+ ExitOnFailure(hr, "failed to allocate string for query");
+
+ // Open and Execute the temp View
+ hr = WcaOpenExecuteView(pwzQuery, phTableView);
+ ExitOnFailure(hr, "failed to openexecute temp view with query %ls", pwzQuery);
+ }
+
+ if (NULL == *phColumns)
+ {
+ // use GetColumnInfo to populate the datatype record
+ er = ::MsiViewGetColumnInfo(*phTableView, MSICOLINFO_TYPES, phColumns);
+ ExitOnWin32Error(er, hr, "failed to columns for table: %ls", wzTable);
+ }
+ AssertSz(::MsiRecordGetFieldCount(*phColumns) == cColumns, "passed in argument does not match number of columns in table");
+
+ //
+ // create the temp record
+ //
+ hTempRec = ::MsiCreateRecord(cColumns);
+ ExitOnNull(hTempRec, hr, E_UNEXPECTED, "could not create temp record for table: %ls", wzTable);
+
+ //
+ // loop through all the columns filling in the data
+ //
+ va_start(args, cColumns);
+ for (i = 1; i <= cColumns; i++)
+ {
+ hr = WcaGetRecordString(*phColumns, i, &pwzData);
+ ExitOnFailure(hr, "failed to get the data type for %d", i);
+
+ // if data type is string write string
+ if (L's' == *pwzData || L'S' == *pwzData || L'g' == *pwzData || L'G' == *pwzData || L'l' == *pwzData || L'L' == *pwzData)
+ {
+ LPCWSTR wz = va_arg(args, WCHAR*);
+
+ // if this is the column that is supposed to be unique add the time stamp on the end
+ if (uiUniquifyColumn == i)
+ {
+ hr = StrAllocFormatted(&pwzUniquify, L"%s%u", wz, ++dwUniquifyValue); // up the count so we have no collisions on the unique name
+ ExitOnFailure(hr, "failed to allocate string for unique column: %d", uiUniquifyColumn);
+
+ wz = pwzUniquify;
+ }
+
+ er = ::MsiRecordSetStringW(hTempRec, i, wz);
+ ExitOnWin32Error(er, hr, "failed to set string value at position %d", i);
+ }
+ // if data type is integer write integer
+ else if (L'i' == *pwzData || L'I' == *pwzData || L'j' == *pwzData || L'J' == *pwzData)
+ {
+ AssertSz(uiUniquifyColumn != i, "Cannot uniquify an integer column");
+ int iData = va_arg(args, int);
+
+ er = ::MsiRecordSetInteger(hTempRec, i, iData);
+ ExitOnWin32Error(er, hr, "failed to set integer value at position %d", i);
+ }
+ else
+ {
+ // not supporting binary streams so error out
+ hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+ ExitOnRootFailure(hr, "unsupported data type '%ls' in column: %d", pwzData, i);
+ }
+ }
+ va_end(args);
+
+ //
+ // add the temporary record to the MSI
+ //
+ er = ::MsiViewModify(*phTableView, MSIMODIFY_INSERT_TEMPORARY, hTempRec);
+ hr = HRESULT_FROM_WIN32(er);
+ if (FAILED(hr))
+ {
+ if (pdbError)
+ {
+ // MSI provides only a generic ERROR_FUNCTION_FAILED if a temporary row
+ // can't be inserted; if we're being asked to provide the detailed error,
+ // get it using the MSIMODIFY_VALIDATE_NEW flag
+ er = ::MsiViewModify(*phTableView, MSIMODIFY_VALIDATE_NEW, hTempRec);
+ hr = HRESULT_FROM_WIN32(er);
+ }
+
+ WCHAR wzBuf[MAX_PATH];
+ DWORD cchBuf = countof(wzBuf);
+ MSIDBERROR dbErr = ::MsiViewGetErrorW(*phTableView, wzBuf, &cchBuf);
+ if (pdbError)
+ {
+ *pdbError = dbErr;
+ }
+ ExitOnFailure(hr, "failed to add temporary row, dberr: %d, err: %ls", dbErr, wzBuf);
+ }
+
+LExit:
+ ReleaseStr(pwzUniquify);
+ ReleaseStr(pwzData);
+ ReleaseStr(pwzQuery);
+
+ return hr;
+}
+
+
+/********************************************************************
+WcaDumpTable - dumps a table to the log file
+
+********************************************************************/
+extern "C" HRESULT WIXAPI WcaDumpTable(
+ __in_z LPCWSTR wzTable
+ )
+{
+ HRESULT hr = S_OK;
+ UINT er = ERROR_SUCCESS;
+
+ LPWSTR pwzQuery = NULL;
+ PMSIHANDLE hView;
+ PMSIHANDLE hColumns;
+ DWORD cColumns = 0;
+ PMSIHANDLE hRec;
+
+ LPWSTR pwzData = NULL;
+ LPWSTR pwzPrint = NULL;
+
+ hr = StrAllocFormatted(&pwzQuery, L"SELECT * FROM `%s`",wzTable);
+ ExitOnFailure(hr, "failed to allocate string for query");
+
+ // Open and Execute the temp View
+ hr = WcaOpenExecuteView(pwzQuery, &hView);
+ ExitOnFailure(hr, "failed to openexecute temp view with query %ls", pwzQuery);
+
+ // Use GetColumnInfo to populate the names of the columns.
+ er = ::MsiViewGetColumnInfo(hView, MSICOLINFO_NAMES, &hColumns);
+ hr = HRESULT_FROM_WIN32(er);
+ ExitOnFailure(hr, "failed to get column info for table: %ls", wzTable);
+
+ cColumns = ::MsiRecordGetFieldCount(hColumns);
+
+ WcaLog(LOGMSG_STANDARD, "--- Begin Table Dump %ls ---", wzTable);
+
+ // Loop through all the columns filling in the data.
+ for (DWORD i = 1; i <= cColumns; i++)
+ {
+ hr = WcaGetRecordString(hColumns, i, &pwzData);
+ ExitOnFailure(hr, "failed to get the column name for %d", i);
+
+ hr = StrAllocConcat(&pwzPrint, pwzData, 0);
+ ExitOnFailure(hr, "Failed to add column name.");
+
+ hr = StrAllocConcat(&pwzPrint, L"\t", 1);
+ ExitOnFailure(hr, "Failed to add column name.");
+ }
+
+ WcaLog(LOGMSG_STANDARD, "%ls", pwzPrint);
+
+ // Now dump the actual rows.
+ while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
+ {
+ if (pwzPrint && *pwzPrint)
+ {
+ *pwzPrint = L'\0';
+ }
+
+ for (DWORD i = 1; i <= cColumns; i++)
+ {
+ hr = WcaGetRecordString(hRec, i, &pwzData);
+ ExitOnFailure(hr, "failed to get the column name for %d", i);
+
+ hr = StrAllocConcat(&pwzPrint, pwzData, 0);
+ ExitOnFailure(hr, "Failed to add column name.");
+
+ hr = StrAllocConcat(&pwzPrint, L"\t", 1);
+ ExitOnFailure(hr, "Failed to add column name.");
+ }
+
+ WcaLog(LOGMSG_STANDARD, "%ls", pwzPrint);
+ }
+
+ WcaLog(LOGMSG_STANDARD, "--- End Table Dump %ls ---", wzTable);
+
+LExit:
+ ReleaseStr(pwzPrint);
+ ReleaseStr(pwzData);
+ ReleaseStr(pwzQuery);
+
+ return hr;
+}
+
+
+HRESULT WIXAPI WcaDeferredActionRequiresReboot()
+{
+ HRESULT hr = S_OK;
+ ATOM atomReboot = 0;
+
+ atomReboot = ::GlobalAddAtomW(L"WcaDeferredActionRequiresReboot");
+ ExitOnNullWithLastError(atomReboot, hr, "Failed to create WcaDeferredActionRequiresReboot global atom.");
+
+LExit:
+ return hr;
+}
+
+
+BOOL WIXAPI WcaDidDeferredActionRequireReboot()
+{
+ // NOTE: This function does not delete the global atom. That is done
+ // purposefully so that any other installs that occur after this point also
+ // require a reboot.
+ ATOM atomReboot = ::GlobalFindAtomW(L"WcaDeferredActionRequiresReboot");
+ return 0 != atomReboot;
+}
diff --git a/src/wcautil/wcawrapquery.cpp b/src/wcautil/wcawrapquery.cpp
new file mode 100644
index 0000000..f04da10
--- /dev/null
+++ b/src/wcautil/wcawrapquery.cpp
@@ -0,0 +1,717 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+#include "wcawrapquery.h"
+
+static const LPWSTR ISINSTALLEDCOLUMNNAME = L"ISInstalled";
+static const LPWSTR ISACTIONCOLUMNNAME = L"ISAction";
+static const LPWSTR SOURCEPATHCOLUMNNAME = L"SourcePath";
+static const LPWSTR TARGETPATHCOLUMNNAME = L"TargetPath";
+
+// This instantiates a new query object in the deferred CA, and returns the handle to the query
+WCA_WRAPQUERY_HANDLE WIXAPI GetNewQueryInstance(
+ DWORD dwInColumns,
+ DWORD dwInRows
+ )
+{
+ HRESULT hr = S_OK;
+
+ WCA_WRAPQUERY_HANDLE hNewHandle = NULL;
+
+ hNewHandle = static_cast(MemAlloc(sizeof(WCA_WRAPQUERY_STRUCT), TRUE));
+ if (NULL == hNewHandle)
+ {
+ hr = E_OUTOFMEMORY;
+ ExitOnFailure(hr, "Failed to allocate Query Instance");
+ }
+
+ // Initialize non-array members
+ hNewHandle->dwColumns = dwInColumns;
+ hNewHandle->dwRows = dwInRows;
+ hNewHandle->dwNextIndex = 0;
+
+ // Initialize arrays
+ if (0 != hNewHandle->dwColumns)
+ {
+ hNewHandle->pcdtColumnType = static_cast(MemAlloc(hNewHandle->dwColumns * sizeof(eColumnDataType), TRUE));
+ if (NULL == hNewHandle->pcdtColumnType)
+ {
+ hr = E_OUTOFMEMORY;
+ ExitOnFailure(hr, "Failed to allocate column type array");
+ }
+
+ hNewHandle->ppwzColumnNames = static_cast(MemAlloc(hNewHandle->dwColumns * sizeof(LPWSTR), TRUE));
+ if (NULL == hNewHandle->ppwzColumnNames)
+ {
+ hr = E_OUTOFMEMORY;
+ ExitOnFailure(hr, "Failed to allocate column names array");
+ }
+ }
+
+ for (DWORD i=0;idwColumns;i++)
+ {
+ hNewHandle->pcdtColumnType[i] = cdtUnknown;
+ hNewHandle->ppwzColumnNames[i] = NULL;
+ }
+
+ if (0 != hNewHandle->dwRows)
+ {
+ hNewHandle->phRecords = static_cast(MemAlloc(hNewHandle->dwRows * sizeof(MSIHANDLE), TRUE));
+ if (NULL == hNewHandle->phRecords)
+ {
+ hr = E_OUTOFMEMORY;
+ ExitOnFailure(hr, "Failed to allocate records array");
+ }
+ }
+
+ for (DWORD i=0;idwRows;i++)
+ {
+ hNewHandle->phRecords[i] = NULL;
+ }
+
+ return hNewHandle;
+
+LExit:
+ // The handle isn't complete, so destroy any memory it allocated before returning NULL
+ if (NULL != hNewHandle)
+ {
+ WcaFinishUnwrapQuery(hNewHandle);
+ }
+
+ return NULL;
+}
+
+// This function takes in the column type string from MsiViewGetColumnInfo, and returns
+// whether the column stores strings, ints, binary streams, or
+// cdtUnknown if this information couldn't be determined.
+eColumnDataType WIXAPI GetDataTypeFromString(
+ LPCWSTR pwzTypeString
+ )
+{
+ if (NULL == pwzTypeString || 0 == wcslen(pwzTypeString))
+ {
+ return cdtUnknown;
+ }
+
+ switch (pwzTypeString[0])
+ {
+ case 'v':
+ case 'V':
+ case 'o':
+ case 'O':
+ return cdtStream;
+
+ case 'g':
+ case 'G':
+ case 's':
+ case 'S':
+ case 'l':
+ case 'L':
+ return cdtString;
+
+ case 'i':
+ case 'I':
+ case 'j':
+ case 'J':
+ return cdtInt;
+
+ default:
+ return cdtUnknown;
+ }
+}
+
+HRESULT WIXAPI WcaWrapEmptyQuery(
+ __inout LPWSTR * ppwzCustomActionData
+ )
+{
+ HRESULT hr = S_OK;
+
+ WcaLog(LOGMSG_TRACEONLY, "Wrapping result of empty query");
+
+ hr = WcaWriteIntegerToCaData(static_cast(wqaTableBegin), ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write table begin marker to custom action data");
+
+ hr = WcaWriteIntegerToCaData(0, ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write number of columns to custom action data");
+
+ hr = WcaWriteIntegerToCaData(0, ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write number of rows to custom action data");
+
+ hr = WcaWriteIntegerToCaData(static_cast(wqaTableFinish), ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write table finish marker to custom action data");
+
+// WcaLog(LOGMSG_TRACEONLY, "Finished wrapping result of empty query");
+
+LExit:
+ return hr;
+}
+
+/********************************************************************
+WcaWrapQuery() - wraps a view and transmits it through the
+ CustomActionData property
+
+********************************************************************/
+HRESULT WIXAPI WcaWrapQuery(
+ __in_z LPCWSTR pwzQuery,
+ __inout LPWSTR * ppwzCustomActionData,
+ __in_opt DWORD dwFormatMask,
+ __in_opt DWORD dwComponentColumn,
+ __in_opt DWORD dwDirectoryColumn
+ )
+{
+ HRESULT hr = S_OK;
+ HRESULT hrTemp = S_OK;
+ UINT er = ERROR_SUCCESS;
+ UINT cViewColumns;
+ eColumnDataType *pcdtColumnTypeList = NULL;
+ LPWSTR pwzData = NULL;
+ LPWSTR pwzColumnData = NULL;
+ LPWSTR pwzRecordData = NULL;
+ BYTE* pbData = NULL;
+ DWORD dwNumRecords = 0;
+ BOOL fAddComponentState = FALSE; // Add two integer columns to the right side of the query - ISInstalled, and ISAction
+ BOOL fAddDirectoryPath = FALSE; // Add two string columns to the right side of the query - SourcePath, and TargetPath
+ int iTempInteger = 0;
+
+ WCHAR wzPath[MAX_PATH + 1];
+ DWORD dwLen;
+ INSTALLSTATE isInstalled = INSTALLSTATE_UNKNOWN;
+ INSTALLSTATE isAction = INSTALLSTATE_UNKNOWN;
+
+ PMSIHANDLE hColumnTypes, hColumnNames;
+ PMSIHANDLE hView, hRec;
+
+ WcaLog(LOGMSG_TRACEONLY, "Wrapping result of query: \"%ls\"", pwzQuery);
+
+ // open the view
+ hr = WcaOpenExecuteView(pwzQuery, &hView);
+ ExitOnFailure(hr, "Failed to execute view");
+
+ hr = WcaWriteIntegerToCaData(static_cast(wqaTableBegin), ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write table begin marker to custom action data");
+
+// WcaLog(LOGMSG_TRACEONLY, "Starting to wrap table's column information", pwzQuery);
+
+ // Use GetColumnInfo to populate the names of the columns.
+ er = ::MsiViewGetColumnInfo(hView, MSICOLINFO_TYPES, &hColumnTypes);
+ ExitOnWin32Error(er, hr, "Failed to get column types");
+
+ er = ::MsiViewGetColumnInfo(hView, MSICOLINFO_NAMES, &hColumnNames);
+ ExitOnWin32Error(er, hr, "Failed to get column names");
+
+ cViewColumns = ::MsiRecordGetFieldCount(hColumnTypes);
+
+ if (0xFFFFFFFF == cViewColumns)
+ {
+ // According to MSDN, this return value only happens when the handle is invalid
+ hr = E_HANDLE;
+ ExitOnFailure(hr, "Failed to get number of fields in record");
+ }
+
+ if (cViewColumns >= dwComponentColumn)
+ {
+ fAddComponentState = TRUE;
+ }
+ else if (0xFFFFFFFF != dwComponentColumn)
+ {
+ hr = E_INVALIDARG;
+ ExitOnFailure(hr, "Component column %d out of range", dwComponentColumn);
+ }
+
+ if (cViewColumns >= dwDirectoryColumn)
+ {
+ fAddDirectoryPath = TRUE;
+ }
+ else if (0xFFFFFFFF != dwDirectoryColumn)
+ {
+ hr = E_INVALIDARG;
+ ExitOnFailure(hr, "Directory column %d out of range", dwDirectoryColumn);
+ }
+
+ hr = WcaWriteIntegerToCaData(static_cast(cViewColumns) + 2 * static_cast(fAddComponentState) + 2 * static_cast(fAddDirectoryPath), ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write number of columns to custom action data");
+
+ pcdtColumnTypeList = new eColumnDataType[cViewColumns];
+ ExitOnNull(pcdtColumnTypeList, hr, E_OUTOFMEMORY, "Failed to allocate memory to store column info types");
+
+ // Loop through all the columns reporting information about each one
+ for (DWORD i = 0; i < cViewColumns; i++)
+ {
+ hr = WcaGetRecordString(hColumnNames, i+1, &pwzData);
+ ExitOnFailure(hr, "Failed to get the column %d name", i+1);
+
+ hr = WcaWriteStringToCaData(pwzData, &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write column %d name %ls to custom action data", i+1, pwzData);
+
+ hr = WcaGetRecordString(hColumnTypes, i+1, &pwzData);
+ ExitOnFailure(hr, "Failed to get the column type string for column %d", i+1);
+
+ pcdtColumnTypeList[i] = GetDataTypeFromString(pwzData);
+
+ if (cdtUnknown == pcdtColumnTypeList[i])
+ {
+ hr = E_INVALIDARG;
+ ExitOnFailure(hr, "Failed to recognize column %d type string: %ls", i+1, pwzData);
+ }
+
+ hr = WcaWriteIntegerToCaData(pcdtColumnTypeList[i], &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write column %d type enumeration to custom action data", i+1);
+ }
+
+ // Add two integer columns to the right side of the query - ISInstalled, and ISAction
+ if (fAddComponentState)
+ {
+ hr = WcaWriteStringToCaData(ISINSTALLEDCOLUMNNAME, &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write extra column %d name %ls to custom action data", cViewColumns + 1, ISINSTALLEDCOLUMNNAME);
+
+ hr = WcaWriteIntegerToCaData(cdtInt, &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write extra column %d type to custom action data", cViewColumns + 1);
+
+ hr = WcaWriteStringToCaData(ISACTIONCOLUMNNAME, &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write extra column %d name %ls to custom action data", cViewColumns + 1, ISACTIONCOLUMNNAME);
+
+ hr = WcaWriteIntegerToCaData(cdtInt, &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write extra column %d type to custom action data", cViewColumns + 1);
+ }
+
+ if (fAddDirectoryPath)
+ {
+ hr = WcaWriteStringToCaData(SOURCEPATHCOLUMNNAME, &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write extra column %d name %ls to custom action data", cViewColumns + 1, SOURCEPATHCOLUMNNAME);
+
+ hr = WcaWriteIntegerToCaData(cdtString, &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write extra column %d type to custom action data", cViewColumns + 1);
+
+ hr = WcaWriteStringToCaData(TARGETPATHCOLUMNNAME, &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write extra column %d name %ls to custom action data", cViewColumns + 1, TARGETPATHCOLUMNNAME);
+
+ hr = WcaWriteIntegerToCaData(cdtString, &pwzColumnData);
+ ExitOnFailure(hr, "Failed to write extra column %d type to custom action data", cViewColumns + 1);
+ }
+
+ // Begin wrapping actual table data
+ //WcaLog(LOGMSG_TRACEONLY, "Starting to wrap table data", pwzQuery);
+ while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
+ {
+ hr = WcaWriteIntegerToCaData(static_cast(wqaRowBegin), &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write row begin marker to custom action data");
+
+ for (DWORD i = 0; i < cViewColumns; i++)
+ {
+ switch (pcdtColumnTypeList[i])
+ {
+ case cdtString:
+ // If we were given a format mask, we're not past the index, and it's set to true for this column, then format the string
+ if (i < (sizeof(dwFormatMask) * 8) && (dwFormatMask & (1 << i)))
+ {
+ hr = WcaGetRecordFormattedString(hRec, i + 1, &pwzData);
+ }
+ else
+ {
+ hr = WcaGetRecordString(hRec, i + 1, &pwzData);
+ }
+ ExitOnFailure(hr, "Failed to get string for column %d", i + 1);
+
+ hr = WcaWriteStringToCaData(pwzData, &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write string to temporary record custom action data for column %d", i + 1);
+ break;
+
+ case cdtInt:
+ if (i < (sizeof(dwFormatMask) * 8) && (dwFormatMask & (1 << i)))
+ {
+ hr = WcaGetRecordFormattedInteger(hRec, i + 1, &iTempInteger);
+ }
+ else
+ {
+ hr = WcaGetRecordInteger(hRec, i + 1, &iTempInteger);
+ }
+ ExitOnFailure(hr, "Failed to get integer for column %d", i + 1);
+
+ hr = WcaWriteIntegerToCaData(iTempInteger, &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write integer to temporary record custom action data for column %d", i + 1);
+ break;
+
+ case cdtStream:
+ hr = E_NOTIMPL;
+ ExitOnFailure(hr, "A query was wrapped which contained a binary stream data field in column %d - however, the ability to wrap stream data fields is not implemented at this time", i);
+ break;
+
+ case cdtUnknown:
+ default:
+ hr = E_INVALIDARG;
+ ExitOnFailure(hr, "Failed to recognize column type enumeration %d for column %d", pcdtColumnTypeList[i], i + 1);
+ }
+ }
+
+ // Add two integer columns to the right side of the query - ISInstalled, and ISAction
+ if (fAddComponentState)
+ {
+ // Get the component ID
+ hr = WcaGetRecordString(hRec, dwComponentColumn, &pwzData);
+ ExitOnFailure(hr, "Failed to get component from column %d while adding extra columns", dwComponentColumn);
+
+ if (0 == lstrlenW(pwzData))
+ {
+ // If no component was provided, set these both to zero as though a structure to store them were allocated with memory zero'd out
+ isInstalled = (INSTALLSTATE)0;
+ isAction = (INSTALLSTATE)0;
+ }
+ else
+ {
+ er = ::MsiGetComponentStateW(WcaGetInstallHandle(), pwzData, &isInstalled, &isAction);
+ // If we don't get the component state, that may be because the component ID was invalid, but isn't necessarily an error, so write NULL's
+ if (FAILED(HRESULT_FROM_WIN32(er)))
+ {
+ ExitOnFailure(hr, "Failed to get component state for component %ls", pwzData);
+ }
+ }
+
+ hr = WcaWriteIntegerToCaData(isInstalled, &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write extra ISInstalled column to custom action data");
+
+ hr = WcaWriteIntegerToCaData(isAction, &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write extra ISAction column to custom action data");
+ }
+
+ // Add two string columns to the right side of the query - SourcePath, and TargetPath
+ if (fAddDirectoryPath)
+ {
+ hr = WcaGetRecordString(hRec, dwDirectoryColumn, &pwzData);
+ // If this fails, ignore it, and just leave those columns blank
+ if (SUCCEEDED(hr))
+ {
+ // Only get source path if the component state is INSTALLSTATE_SOURCE, or if we have no component to check the installstate of
+ if (INSTALLSTATE_SOURCE == isAction || !fAddComponentState)
+ {
+ dwLen = countof(wzPath);
+ er = ::MsiGetSourcePathW(WcaGetInstallHandle(), pwzData, wzPath, &dwLen);
+ hrTemp = HRESULT_FROM_WIN32(er);
+ if (dwLen > countof(wzPath))
+ {
+ hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ ExitOnRootFailure(hr, "Failed to record entire Source Path for Directory %ls because its length was greater than MAX_PATH.", pwzData);
+ }
+
+ if (SUCCEEDED(hrTemp))
+ {
+ hr = WcaWriteStringToCaData(wzPath, &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write source path string to record data string");
+ }
+ else
+ {
+ hr = WcaWriteStringToCaData(L"", &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write empty source path string to record data string");
+ }
+ }
+ else
+ {
+ hr = WcaWriteStringToCaData(L"", &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write empty source path string before writing target path string to record data string");
+ }
+
+ dwLen = countof(wzPath);
+ er = ::MsiGetTargetPathW(WcaGetInstallHandle(), pwzData, wzPath, &dwLen);
+ hrTemp = HRESULT_FROM_WIN32(er);
+ if (dwLen > countof(wzPath))
+ {
+ hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ ExitOnRootFailure(hr, "Failed to record entire Source Path for Directory %ls because its length was greater than MAX_PATH.", pwzData);
+ }
+ if (SUCCEEDED(hrTemp))
+ {
+ hr = WcaWriteStringToCaData(wzPath, &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write target path string to record data string");
+ }
+ else
+ {
+ hr = WcaWriteStringToCaData(L"", &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write empty target path string to record data string");
+ }
+ }
+ else
+ {
+ // Write both fields as blank
+ hr = WcaWriteStringToCaData(L"", &pwzRecordData);
+ hr = WcaWriteStringToCaData(L"", &pwzRecordData);
+ }
+ }
+
+ hr = WcaWriteIntegerToCaData(static_cast(wqaRowFinish), &pwzRecordData);
+ ExitOnFailure(hr, "Failed to write row finish marker to custom action data");
+
+ ++dwNumRecords;
+ }
+
+ hr = WcaWriteIntegerToCaData(dwNumRecords, ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write number of records to custom action data");
+
+ if (NULL != pwzColumnData)
+ {
+ hr = WcaWriteStringToCaData(pwzColumnData, ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write column data to custom action data");
+ }
+
+ if (NULL != pwzRecordData)
+ {
+ hr = WcaWriteStringToCaData(pwzRecordData, ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write record data to custom action data");
+ }
+
+ hr = WcaWriteIntegerToCaData(static_cast(wqaTableFinish), ppwzCustomActionData);
+ ExitOnFailure(hr, "Failed to write table finish marker to custom action data");
+
+// WcaLog(LOGMSG_TRACEONLY, "Finished wrapping result of query: \"%ls\"", pwzQuery);
+
+LExit:
+ ReleaseStr(pwzData);
+ ReleaseStr(pwzColumnData);
+ ReleaseStr(pwzRecordData);
+
+ ReleaseMem(pbData);
+
+ return hr;
+}
+
+
+/********************************************************************
+WcaBeginUnwrapQuery() - unwraps a view for direct access from the
+ CustomActionData property
+
+********************************************************************/
+HRESULT WIXAPI WcaBeginUnwrapQuery(
+ __out WCA_WRAPQUERY_HANDLE * phWrapQuery,
+ __inout LPWSTR * ppwzCustomActionData
+ )
+{
+ HRESULT hr = S_OK;
+ int iTempInteger = 0;
+ int iColumns = 0;
+ int iRows = 0;
+ BYTE* pbData = NULL;
+ LPWSTR pwzData = NULL;
+ WCA_WRAPQUERY_HANDLE hWrapQuery = NULL;
+
+ WcaLog(LOGMSG_TRACEONLY, "Unwrapping a query from custom action data");
+
+ hr = WcaReadIntegerFromCaData(ppwzCustomActionData, &iTempInteger);
+ if (wqaTableBegin != iTempInteger)
+ {
+ hr = E_INVALIDARG;
+ }
+ ExitOnFailure(hr, "Failed to read table begin marker from custom action data (read %d instead)", iTempInteger);
+
+ hr = WcaReadIntegerFromCaData(ppwzCustomActionData, &iColumns);
+ ExitOnFailure(hr, "Failed to read number of columns from custom action data");
+
+ hr = WcaReadIntegerFromCaData(ppwzCustomActionData, &iRows);
+ ExitOnFailure(hr, "Failed to read number of rows from custom action data");
+
+ hWrapQuery = GetNewQueryInstance(iColumns, iRows);
+ if (NULL == hWrapQuery)
+ {
+ hr = E_POINTER;
+ }
+ ExitOnFailure(hr, "Failed to get a query instance with %d columns and %d rows", iColumns, iRows);
+
+ for (int i = 0; i < iColumns; i++)
+ {
+ hr = WcaReadStringFromCaData(ppwzCustomActionData, &(hWrapQuery->ppwzColumnNames[i]));
+ ExitOnFailure(hr, "Failed to read column %d's name from custom action data", i+1);
+
+ hr = WcaReadIntegerFromCaData(ppwzCustomActionData, &iTempInteger);
+ if (cdtString != iTempInteger && cdtInt != iTempInteger && cdtStream != iTempInteger)
+ {
+ hr = E_INVALIDARG;
+ }
+ ExitOnFailure(hr, "Failed to read column %d's type from custom action data", i+1);
+
+ // Set the column type into the actual data structure
+ hWrapQuery->pcdtColumnType[i] = (eColumnDataType)iTempInteger;
+ }
+
+ for (int i = 0; i < iRows; i++)
+ {
+ hr = WcaReadIntegerFromCaData(ppwzCustomActionData, &iTempInteger);
+ if (wqaRowBegin != iTempInteger)
+ {
+ hr = E_INVALIDARG;
+ }
+ ExitOnFailure(hr, "Failed to read begin row marker from custom action data (read %d instead)", iTempInteger);
+
+ hWrapQuery->phRecords[i] = ::MsiCreateRecord((unsigned int)iColumns);
+
+ for (int j = 0; j < iColumns; j++)
+ {
+ switch (hWrapQuery->pcdtColumnType[j])
+ {
+ case cdtString:
+ hr = WcaReadStringFromCaData(ppwzCustomActionData, &pwzData);
+ ExitOnFailure(hr, "Failed to read string from custom action data");
+
+ hr = WcaSetRecordString(hWrapQuery->phRecords[i], j+1, pwzData);
+ ExitOnFailure(hr, "Failed to write string %ls to record in column %d", pwzData, j+1);
+ break;
+
+ case cdtInt:
+ WcaReadIntegerFromCaData(ppwzCustomActionData, &iTempInteger);
+ ExitOnFailure(hr, "Failed to read integer from custom action data");
+
+ WcaSetRecordInteger(hWrapQuery->phRecords[i], j+1, iTempInteger);
+ ExitOnFailure(hr, "Failed to write integer %d to record in column %d", iTempInteger, j+1);
+ break;
+
+ case cdtStream:
+ hr = E_NOTIMPL;
+ ExitOnFailure(hr, "A query was wrapped which contained a stream data field - however, the ability to wrap stream data fields is not implemented at this time");
+ break;
+
+ case cdtUnknown:
+ default:
+ hr = E_INVALIDARG;
+ ExitOnFailure(hr, "Failed to recognize column type enumeration %d for column %d", hWrapQuery->pcdtColumnType[j+1], i+1);
+ }
+ }
+
+ hr = WcaReadIntegerFromCaData(ppwzCustomActionData, &iTempInteger);
+ if (wqaRowFinish != iTempInteger)
+ {
+ hr = E_INVALIDARG;
+ }
+ ExitOnFailure(hr, "Failed to read row finish marker from custom action data (read %d instead)", iTempInteger);
+ }
+
+ hr = WcaReadIntegerFromCaData(ppwzCustomActionData, &iTempInteger);
+ if (wqaTableFinish != iTempInteger)
+ {
+ hr = E_INVALIDARG;
+ }
+ ExitOnFailure(hr, "Failed to read table finish marker from custom action data (read %d instead)", iTempInteger);
+
+ *phWrapQuery = hWrapQuery;
+
+// WcaLog(LOGMSG_TRACEONLY, "Successfully finished unwrapping a query from custom action data");
+
+LExit:
+ ReleaseStr(pwzData);
+ ReleaseMem(pbData);
+
+ return hr;
+}
+
+// This function returns the total number of records in a query
+DWORD WIXAPI WcaGetQueryRecords(
+ __in const WCA_WRAPQUERY_HANDLE hWrapQuery
+ )
+{
+ return hWrapQuery->dwRows;
+}
+
+// This function resets a query back to its first row, so that the next fetch returns the first record
+void WIXAPI WcaFetchWrappedReset(
+ __in WCA_WRAPQUERY_HANDLE hWrapQuery
+ )
+{
+ hWrapQuery->dwNextIndex = 0;
+}
+
+// Fetches the next record in the query
+// NOTE: the MSIHANDLE returned by this function should not be released, as it is the same handle used by the query object to maintain the item.
+// so, don't use this function with PMSIHANDLE objects!
+HRESULT WIXAPI WcaFetchWrappedRecord(
+ __in WCA_WRAPQUERY_HANDLE hWrapQuery,
+ __out MSIHANDLE* phRec
+ )
+{
+ DWORD dwNextIndex = hWrapQuery->dwNextIndex;
+
+ if (dwNextIndex >= hWrapQuery->dwRows)
+ {
+ return E_NOMOREITEMS;
+ }
+
+ if (NULL == hWrapQuery->phRecords[dwNextIndex])
+ {
+ return E_HANDLE;
+ }
+
+ *phRec = hWrapQuery->phRecords[hWrapQuery->dwNextIndex];
+
+ // Increment our next index variable
+ ++hWrapQuery->dwNextIndex;
+
+ return S_OK;
+}
+
+// Fetch the next record in the query where the string value in column dwComparisonColumn equals the value pwzExpectedValue
+// NOTE: the MSIHANDLE returned by this function should not be released, as it is the same handle used by the query object to maintain the item.
+// so, don't use this function with PMSIHANDLE objects!
+HRESULT WIXAPI WcaFetchWrappedRecordWhereString(
+ __in WCA_WRAPQUERY_HANDLE hWrapQuery,
+ __in DWORD dwComparisonColumn,
+ __in_z LPCWSTR pwzExpectedValue,
+ __out MSIHANDLE* phRec
+ )
+{
+ HRESULT hr = S_OK;
+ MSIHANDLE hRec = NULL;
+ LPWSTR pwzData = NULL;
+
+ while (S_OK == (hr = WcaFetchWrappedRecord(hWrapQuery, &hRec)))
+ {
+ ExitOnFailure(hr, "Failed to fetch a wrapped record");
+
+ hr = WcaGetRecordString(hRec, dwComparisonColumn, &pwzData);
+ ExitOnFailure(hr, "Failed to get record string in column %d", dwComparisonColumn);
+
+ if (0 == lstrcmpW(pwzData, pwzExpectedValue))
+ {
+ *phRec = hRec;
+ ExitFunction1(hr = S_OK);
+ }
+ }
+ // If we errored here but not because there were no records left, write an error to the log
+ if (hr != E_NOMOREITEMS)
+ {
+ ExitOnFailure(hr, "Failed while searching for a wrapped record where column %d is set to %ls", dwComparisonColumn, pwzExpectedValue);
+ }
+
+LExit:
+ ReleaseStr(pwzData);
+
+ return hr;
+}
+
+/********************************************************************
+WcaBeginUnwrapQuery() - Finishes unwrapping a view for direct access
+ from the CustomActionData property
+
+********************************************************************/
+void WIXAPI WcaFinishUnwrapQuery(
+ __in_opt WCA_WRAPQUERY_HANDLE hWrapQuery
+ )
+{
+ if (NULL == hWrapQuery)
+ {
+ WcaLog(LOGMSG_TRACEONLY, "Failed to finish an unwrap query - ignoring");
+ return;
+ }
+
+ ReleaseMem(hWrapQuery->pcdtColumnType);
+
+ for (DWORD i=0;idwColumns;i++)
+ {
+ ReleaseStr(hWrapQuery->ppwzColumnNames[i]);
+ }
+ ReleaseMem(hWrapQuery->ppwzColumnNames);
+
+ for (DWORD i=0;idwRows;i++)
+ {
+ if (NULL != hWrapQuery->phRecords[i])
+ {
+ ::MsiCloseHandle(hWrapQuery->phRecords[i]);
+ }
+ }
+ ReleaseMem(hWrapQuery->phRecords);
+
+ ReleaseMem(hWrapQuery);
+}
diff --git a/version.json b/version.json
new file mode 100644
index 0000000..5f85777
--- /dev/null
+++ b/version.json
@@ -0,0 +1,11 @@
+{
+ "version": "4.0",
+ "publicReleaseRefSpec": [
+ "^refs/heads/master$"
+ ],
+ "cloudBuild": {
+ "buildNumber": {
+ "enabled": true
+ }
+ }
+}
diff --git a/wcautil.sln b/wcautil.sln
new file mode 100644
index 0000000..1b6d9e3
--- /dev/null
+++ b/wcautil.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26730.12
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wcautil", "src\wcautil\wcautil.vcxproj", "{1244E671-F108-4334-BA52-8A7517F26ECD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|x64.ActiveCfg = Debug|x64
+ {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|x64.Build.0 = Debug|x64
+ {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|x86.ActiveCfg = Debug|Win32
+ {1244E671-F108-4334-BA52-8A7517F26ECD}.Debug|x86.Build.0 = Debug|Win32
+ {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|x64.ActiveCfg = Release|x64
+ {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|x64.Build.0 = Release|x64
+ {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|x86.ActiveCfg = Release|Win32
+ {1244E671-F108-4334-BA52-8A7517F26ECD}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {DD209744-C40E-4C34-8CB4-BC6B71F9A133}
+ EndGlobalSection
+EndGlobal